\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/google_drive/res_config_user_view.xml b/addons/google_drive/res_config_user_view.xml
index 728ef03c5dd..dc563a76c2b 100644
--- a/addons/google_drive/res_config_user_view.xml
+++ b/addons/google_drive/res_config_user_view.xml
@@ -23,6 +23,7 @@
+
@@ -54,7 +55,7 @@
- Google Drive Templates
+ Templates
google.drive.config
ir.actions.act_window
form
@@ -87,7 +88,7 @@
-
+
diff --git a/addons/google_drive/static/src/js/gdrive.js b/addons/google_drive/static/src/js/gdrive.js
index 3267487b4e3..dbbe50d0794 100644
--- a/addons/google_drive/static/src/js/gdrive.js
+++ b/addons/google_drive/static/src/js/gdrive.js
@@ -29,19 +29,24 @@ openerp.google_drive = function (instance, m) {
ds.call('get_google_drive_config', [view.dataset.model, res_id, context]).done(function (r) {
if (!_.isEmpty(r)) {
_.each(r, function (res) {
- var g_item = _.indexOf(_.pluck(self.items.other, 'label'), res.name);
- if (g_item !== -1) {
- self.items.other.splice(g_item, 1);
+ var already_there = false;
+ for (var i = 0;i < self.items.other.length;i++){
+ if (self.items.other[i].classname === "oe_share_gdoc" && self.items.other[i].label.indexOf(res.name) > -1){
+ already_there = true;
+ break;
+ }
+ }
+ if (!already_there){
+ self.add_items('other', [{
+ label: res.name+ '
',
+ config_id: res.id,
+ res_id: res_id,
+ res_model: view.dataset.model,
+ callback: self.on_google_doc,
+ classname: 'oe_share_gdoc'
+ },
+ ]);
}
- self.add_items('other', [{
- label: res.name+ '
',
- config_id: res.id,
- res_id: res_id,
- res_model: view.dataset.model,
- callback: self.on_google_doc,
- classname: 'oe_share_gdoc'
- },
- ]);
})
}
});
diff --git a/addons/google_spreadsheet/__init__.py b/addons/google_spreadsheet/__init__.py
new file mode 100644
index 00000000000..f57aaed7749
--- /dev/null
+++ b/addons/google_spreadsheet/__init__.py
@@ -0,0 +1 @@
+import google_spreadsheet
\ No newline at end of file
diff --git a/addons/google_spreadsheet/__openerp__.py b/addons/google_spreadsheet/__openerp__.py
new file mode 100644
index 00000000000..1ae2d48d349
--- /dev/null
+++ b/addons/google_spreadsheet/__openerp__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (
).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+
+{
+ 'name': 'Google Spreadsheet',
+ 'version': '1.0',
+ 'category': 'Tools',
+ 'description': """
+The module adds the possibility to display data from OpenERP in Google Spreadsheets in real time.
+========================================
+""",
+ 'author': 'OpenERP SA',
+ 'website': 'http://www.openerp.com',
+ 'depends': ['board', 'google_drive'],
+ 'js': [
+ 'static/src/js/search.js',
+ ],
+ 'qweb': ['static/src/xml/*.xml'],
+ 'data': ['google_spreadsheet_view.xml', 'google_spreadsheet_data.xml'],
+ 'demo': [],
+ 'installable': True,
+ 'auto_install': False,
+}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/google_spreadsheet/google_spreadsheet.py b/addons/google_spreadsheet/google_spreadsheet.py
new file mode 100644
index 00000000000..693300904d0
--- /dev/null
+++ b/addons/google_spreadsheet/google_spreadsheet.py
@@ -0,0 +1,119 @@
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2012 OpenERP SA ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+import simplejson
+import logging
+from lxml import etree
+import re
+import urllib
+import urllib2
+
+from openerp.osv import osv
+from openerp import SUPERUSER_ID
+
+_logger = logging.getLogger(__name__)
+
+class config(osv.osv):
+ _inherit = 'google.drive.config'
+
+ def get_google_scope(self):
+ scope = super(config, self).get_google_scope()
+ return '%s https://spreadsheets.google.com/feeds' % scope
+
+ def write_config_formula(self, cr, uid, attachment_id, spreadsheet_key, model, domain, groupbys, view_id, context=None):
+ access_token = self.get_access_token(cr, uid, scope='https://spreadsheets.google.com/feeds', context=context)
+
+ fields = self.pool.get(model).fields_view_get(cr, uid, view_id=view_id, view_type='tree')
+ doc = etree.XML(fields.get('arch'))
+ display_fields = []
+ for node in doc.xpath("//field"):
+ if node.get('modifiers'):
+ modifiers = simplejson.loads(node.get('modifiers'))
+ if not modifiers.get('invisible') and not modifiers.get('tree_invisible'):
+ display_fields.append(node.get('name'))
+ fields = " ".join(display_fields)
+ domain = domain.replace("'", r"\'").replace('"', "'")
+ if groupbys:
+ fields = "%s %s" % (groupbys, fields)
+ formula = '=oe_read_group("%s";"%s";"%s";"%s")' % (model, fields, groupbys, domain)
+ else:
+ formula = '=oe_browse("%s";"%s";"%s")' % (model, fields, domain)
+ url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
+ dbname = cr.dbname
+ user = self.pool['res.users'].read(cr, uid, uid, ['login', 'password'], context=context)
+ username = user['login']
+ password = user['password']
+ if self.pool['ir.module.module'].search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_crypt'), ('state', '=', 'installed')]) == 1:
+ config_formula = '=oe_settings("%s";"%s")' % (url, dbname)
+ else:
+ config_formula = '=oe_settings("%s";"%s";"%s";"%s")' % (url, dbname, username, password)
+ request = '''
+ https://spreadsheets.google.com/feeds/cells/%s/od6/private/full
+
+ A1
+
+ https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R1C1
+
+
+
+
+ A2
+
+ https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R60C15
+
+
+
+ ''' % (spreadsheet_key, spreadsheet_key, spreadsheet_key, formula.replace('"', '"'), spreadsheet_key, spreadsheet_key, config_formula.replace('"', '"'))
+
+ try:
+ req = urllib2.Request(
+ 'https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?%s' % (spreadsheet_key, urllib.urlencode({'v': 3, 'access_token': access_token})),
+ data=request,
+ headers={'content-type': 'application/atom+xml', 'If-Match': '*'})
+ urllib2.urlopen(req)
+ except (urllib2.HTTPError, urllib2.URLError):
+ _logger.warning("An error occured while writting the formula on the Google Spreadsheet.")
+
+ description = '''
+ formula: %s
+ ''' % formula
+ if attachment_id:
+ self.pool['ir.attachment'].write(cr, uid, attachment_id, {'description': description}, context=context)
+ return True
+
+ def set_spreadsheet(self, cr, uid, model, domain, groupbys, view_id, context=None):
+ try:
+ config_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'google_spreadsheet', 'google_spreadsheet_template')[1]
+ except ValueError:
+ raise
+ config = self.browse(cr, uid, config_id, context=context)
+ title = 'Spreadsheet %s' % model
+ res = self.copy_doc(cr, uid, False, config.google_drive_resource_id, title, model, context=context)
+
+ mo = re.search("(key=|/d/)([A-Za-z0-9-_]+)", res['url'])
+ if mo:
+ key = mo.group(2)
+
+ self.write_config_formula(cr, uid, res.get('id'), key, model, domain, groupbys, view_id, context=context)
+ return res
diff --git a/addons/google_spreadsheet/google_spreadsheet_data.xml b/addons/google_spreadsheet/google_spreadsheet_data.xml
new file mode 100644
index 00000000000..19802e9d140
--- /dev/null
+++ b/addons/google_spreadsheet/google_spreadsheet_data.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ Base Spreadsheet Template
+
+ https://docs.google.com/spreadsheet/ccc?key=0ApGVjjwUC-ygdDZ0TG5EQnRlLVFQNlFGdFN5b1ZrY1E
+ Reporting %(name)s
+
+
+
+
\ No newline at end of file
diff --git a/addons/google_spreadsheet/google_spreadsheet_view.xml b/addons/google_spreadsheet/google_spreadsheet_view.xml
new file mode 100644
index 00000000000..e99826dae52
--- /dev/null
+++ b/addons/google_spreadsheet/google_spreadsheet_view.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+ ir.attachment.google.spreadsheet.tree
+ ir.attachment
+
+
+
+
+
+
+
+
+
+ ir.attachment.google.spreadsheet.form
+ ir.attachment
+
+
+
+
+
+
+ Google Spreadsheets
+ ir.attachment
+ form
+ tree,form
+ {}
+ [('url', 'like', '/spreadsheet/')]
+ Google Spreadsheets
+
+
+
+
+ tree
+
+
+
+
+
+
+ form
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/google_spreadsheet/i18n/google_spreadsheet.pot b/addons/google_spreadsheet/i18n/google_spreadsheet.pot
new file mode 100644
index 00000000000..3cf39bd8df5
--- /dev/null
+++ b/addons/google_spreadsheet/i18n/google_spreadsheet.pot
@@ -0,0 +1,42 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * google_spreadsheet
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 8.0alpha1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2013-07-25 12:39+0000\n"
+"PO-Revision-Date: 2013-07-25 12:39+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: google_spreadsheet
+#: model:ir.actions.act_window,help:google_spreadsheet.action_ir_attachment_google_spreadsheet_tree
+#: model:ir.actions.act_window,name:google_spreadsheet.action_ir_attachment_google_spreadsheet_tree
+#: view:ir.attachment:0
+#: model:ir.ui.menu,name:google_spreadsheet.menu_reporting_dashboard_google_spreadsheets
+msgid "Google Spreadsheets"
+msgstr ""
+
+#. module: google_spreadsheet
+#. openerp-web
+#: code:addons/google_spreadsheet/static/src/xml/addtospreadsheet.xml:3
+#, python-format
+msgid "Add to Google Spreadsheet"
+msgstr ""
+
+#. module: google_spreadsheet
+#: view:ir.attachment:0
+msgid "Name"
+msgstr ""
+
+#. module: google_spreadsheet
+#: model:ir.model,name:google_spreadsheet.model_google_drive_config
+msgid "Google Drive templates config"
+msgstr ""
+
diff --git a/addons/google_spreadsheet/static/src/js/search.js b/addons/google_spreadsheet/static/src/js/search.js
new file mode 100644
index 00000000000..c46ec615792
--- /dev/null
+++ b/addons/google_spreadsheet/static/src/js/search.js
@@ -0,0 +1,54 @@
+openerp.google_spreadsheet = function(instance) {
+ var _t = instance.web._t;
+ instance.web.FormView.include({
+ on_processed_onchange: function(result, processed) {
+ var self = this;
+
+ var fields = self.fields;
+ _(result.selection).each(function (selection, fieldname) {
+ var field = fields[fieldname];
+ if (!field) { return; }
+ field.field.selection = selection;
+ field.values = selection;
+ field.renderElement();
+ });
+ return this._super(result, processed);
+ },
+ });
+ instance.board.AddToGoogleSpreadsheet = instance.web.search.Input.extend({
+ template: 'SearchView.addtogooglespreadsheet',
+ _in_drawer: true,
+ start: function () {
+ var self = this;
+ this.$el.on('click', 'h4', function(){
+ var view = self.view;
+ var data = view.build_search_data();
+ var model = view.model;
+ var list_view = self.view.getParent().views['list'];
+ var view_id = list_view ? list_view.view_id : false;
+ var context = new instance.web.CompoundContext(view.dataset.get_context() || []);
+ var domain = new instance.web.CompoundDomain(view.dataset.get_domain() || []);
+ _.each(data.contexts, context.add, context);
+ _.each(data.domains, domain.add, domain);
+ domain = JSON.stringify(domain.eval());
+ var groupbys = instance.web.pyeval.eval('groupbys', data.groupbys).join(" ");
+ var view_id = view_id;
+ var ds = new instance.web.DataSet(self, 'google.drive.config');
+ ds.call('set_spreadsheet', [model, domain, groupbys, view_id]).done(function (res) {
+ if (res['url']){
+ window.open(res['url'], '_blank');
+ }
+ });
+ });
+ },
+ });
+ instance.web.SearchView.include({
+ add_common_inputs: function() {
+ this._super();
+ var vm = this.getParent().getParent();
+ if (vm.inner_action && vm.inner_action.views) {
+ (new instance.board.AddToGoogleSpreadsheet(this));
+ }
+ }
+ });
+};
\ No newline at end of file
diff --git a/addons/google_spreadsheet/static/src/xml/addtospreadsheet.xml b/addons/google_spreadsheet/static/src/xml/addtospreadsheet.xml
new file mode 100644
index 00000000000..faf984141a6
--- /dev/null
+++ b/addons/google_spreadsheet/static/src/xml/addtospreadsheet.xml
@@ -0,0 +1,5 @@
+
+
+
Add to Google Spreadsheet
+
+
\ No newline at end of file
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/doc/changelog.rst b/addons/hr_holidays/doc/changelog.rst
new file mode 100644
index 00000000000..b286a60d1b5
--- /dev/null
+++ b/addons/hr_holidays/doc/changelog.rst
@@ -0,0 +1,13 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+ - updated ``hr.holidays`` workflow. It now starts in ``confirm`` state. In
+ ``confirm```and ``refuse`` a Reset to Draft has been added in view / workflow,
+ allowing to edit the request. Added a ``can_reset`` computed field to enable
+ this transition. A user can edit its own requests, or all requests if he is
+ an Hr Manager.
diff --git a/addons/hr_holidays/doc/index.rst b/addons/hr_holidays/doc/index.rst
new file mode 100644
index 00000000000..dc87c275b74
--- /dev/null
+++ b/addons/hr_holidays/doc/index.rst
@@ -0,0 +1,13 @@
+Hr Holidays module documentation
+================================
+
+Hr Holidays documentation topics
+''''''''''''''''''''''''''''''''
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py
index f2be4844d27..008c711a1f0 100644
--- a/addons/hr_holidays/hr_holidays.py
+++ b/addons/hr_holidays/hr_holidays.py
@@ -22,11 +22,10 @@
##############################################################################
import datetime
-import time
-from itertools import groupby
-from operator import attrgetter, itemgetter
-
import math
+import time
+from operator import attrgetter
+
from openerp import tools
from openerp.osv import fields, osv
from openerp.tools.translate import _
@@ -36,40 +35,37 @@ class hr_holidays_status(osv.osv):
_name = "hr.holidays.status"
_description = "Leave Type"
- def get_days(self, cr, uid, ids, employee_id, return_false, context=None):
- cr.execute("""SELECT id, type, number_of_days, holiday_status_id FROM hr_holidays WHERE employee_id = %s AND state='validate' AND holiday_status_id in %s""",
- [employee_id, tuple(ids)])
- result = sorted(cr.dictfetchall(), key=lambda x: x['holiday_status_id'])
- grouped_lines = dict((k, [v for v in itr]) for k, itr in groupby(result, itemgetter('holiday_status_id')))
- res = {}
- for record in self.browse(cr, uid, ids, context=context):
- res[record.id] = {}
- max_leaves = leaves_taken = 0
- if not return_false:
- if record.id in grouped_lines:
- leaves_taken = -sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'remove'])
- max_leaves = sum([item['number_of_days'] for item in grouped_lines[record.id] if item['type'] == 'add'])
- res[record.id]['max_leaves'] = max_leaves
- res[record.id]['leaves_taken'] = leaves_taken
- res[record.id]['remaining_leaves'] = max_leaves - leaves_taken
- return res
+ def get_days(self, cr, uid, ids, employee_id, context=None):
+ result = dict((id, dict(max_leaves=0, leaves_taken=0, remaining_leaves=0,
+ virtual_remaining_leaves=0)) for id in ids)
+ holiday_ids = self.pool['hr.holidays'].search(cr, uid, [('employee_id', '=', employee_id),
+ ('state', 'in', ['confirm', 'validate1', 'validate']),
+ ('holiday_status_id', 'in', ids)
+ ], context=context)
+ for holiday in self.pool['hr.holidays'].browse(cr, uid, holiday_ids, context=context):
+ status_dict = result[holiday.holiday_status_id.id]
+ if holiday.type == 'add':
+ status_dict['virtual_remaining_leaves'] += holiday.number_of_days
+ if holiday.state == 'validate':
+ status_dict['max_leaves'] += holiday.number_of_days
+ status_dict['remaining_leaves'] += holiday.number_of_days
+ elif holiday.type == 'remove': # number of days is negative
+ status_dict['virtual_remaining_leaves'] += holiday.number_of_days
+ if holiday.state == 'validate':
+ status_dict['leaves_taken'] -= holiday.number_of_days
+ status_dict['remaining_leaves'] += holiday.number_of_days
+ return result
def _user_left_days(self, cr, uid, ids, name, args, context=None):
- return_false = False
employee_id = False
- res = {}
- if context and context.has_key('employee_id'):
- if not context['employee_id']:
- return_false = True
+ if context and 'employee_id' in context:
employee_id = context['employee_id']
else:
- employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
+ employee_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
if employee_ids:
employee_id = employee_ids[0]
- else:
- return_false = True
if employee_id:
- res = self.get_days(cr, uid, ids, employee_id, return_false, context=context)
+ res = self.get_days(cr, uid, ids, employee_id, context=context)
else:
res = dict.fromkeys(ids, {'leaves_taken': 0, 'remaining_leaves': 0, 'max_leaves': 0})
return res
@@ -79,11 +75,12 @@ 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'),
'remaining_leaves': fields.function(_user_left_days, string='Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken', multi='user_left_days'),
+ 'virtual_remaining_leaves': fields.function(_user_left_days, string='Virtual Remaining Leaves', help='Maximum Leaves Allowed - Leaves Already Taken - Leaves Waiting Approval', multi='user_left_days'),
'double_validation': fields.boolean('Apply Double Validation', help="When selected, the Allocation/Leave Requests for this type require a second validation to be approved."),
}
_defaults = {
@@ -92,8 +89,6 @@ class hr_holidays_status(osv.osv):
}
def name_get(self, cr, uid, ids, context=None):
- if not ids:
- return []
res = []
for record in self.browse(cr, uid, ids, context=context):
name = record.name
@@ -134,6 +129,19 @@ class hr_holidays(osv.osv):
result[hol.id] = hol.number_of_days_temp
return result
+ def _get_can_reset(self, cr, uid, ids, name, arg, context=None):
+ """User can reset a leave request if it is its own leave request or if
+ he is an Hr Manager. """
+ user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+ group_hr_manager_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')[1]
+ if group_hr_manager_id in [g.id for g in user.groups_id]:
+ return dict.fromkeys(ids, True)
+ result = dict.fromkeys(ids, False)
+ for holiday in self.browse(cr, uid, ids, context=context):
+ if holiday.employee_id and holiday.employee_id.user_id and holiday.employee_id.user_id.id == uid:
+ result[holiday.id] = True
+ return result
+
def _check_date(self, cr, uid, ids):
for holiday in self.browse(cr, uid, ids):
holiday_ids = self.search(cr, uid, [('date_from', '<=', holiday.date_to), ('date_to', '>=', holiday.date_from), ('employee_id', '=', holiday.employee_id.id), ('id', '<>', holiday.id)])
@@ -167,10 +175,13 @@ class hr_holidays(osv.osv):
'holiday_type': fields.selection([('employee','By Employee'),('category','By Employee Tag')], 'Allocation Mode', readonly=True, states={'draft':[('readonly',False)], 'confirm':[('readonly',False)]}, help='By Employee: Allocation/Request for individual Employee, By Employee Tag: Allocation/Request for group of employees in category', required=True),
'manager_id2': fields.many2one('hr.employee', 'Second Approval', readonly=True, help='This area is automaticly filled by the user who validate the leave with second level (If Leave type need second validation)'),
'double_validation': fields.related('holiday_status_id', 'double_validation', type='boolean', relation='hr.holidays.status', string='Apply Double Validation'),
+ 'can_reset': fields.function(
+ _get_can_reset,
+ type='boolean'),
}
_defaults = {
'employee_id': _employee_get,
- 'state': 'draft',
+ 'state': 'confirm',
'type': 'remove',
'user_id': lambda obj, cr, uid, context: uid,
'holiday_type': 'employee'
@@ -302,27 +313,20 @@ class hr_holidays(osv.osv):
if context is None:
context = {}
context = dict(context, mail_create_nolog=True)
- return super(hr_holidays, self).create(cr, uid, values, context=context)
+ hol_id = super(hr_holidays, self).create(cr, uid, values, context=context)
+ self.check_holidays(cr, uid, [hol_id], context=context)
+ return hol_id
- def write(self, cr, uid, ids, vals, context=None):
- check_fnct = self.pool.get('hr.holidays.status').check_access_rights
- for holiday in self.browse(cr, uid, ids, context=context):
- if holiday.state in ('validate','validate1') and not check_fnct(cr, uid, 'write', raise_exception=False):
- raise osv.except_osv(_('Warning!'),_('You cannot modify a leave request that has been approved. Contact a human resource manager.'))
- return super(hr_holidays, self).write(cr, uid, ids, vals, context=context)
-
- def set_to_draft(self, cr, uid, ids, context=None):
+ def holidays_reset(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {
'state': 'draft',
'manager_id': False,
'manager_id2': False,
})
- self.delete_workflow(cr, uid, ids)
- self.create_workflow(cr, uid, ids)
to_unlink = []
for record in self.browse(cr, uid, ids, context=context):
for record2 in record.linked_request_ids:
- self.set_to_draft(cr, uid, [record2.id], context=context)
+ self.holidays_reset(cr, uid, [record2.id], context=context)
to_unlink.append(record2.id)
if to_unlink:
self.unlink(cr, uid, to_unlink, context=context)
@@ -421,13 +425,16 @@ class hr_holidays(osv.osv):
return True
def check_holidays(self, cr, uid, ids, context=None):
- holi_status_obj = self.pool.get('hr.holidays.status')
- for record in self.browse(cr, uid, ids):
- if record.holiday_type == 'employee' and record.type == 'remove':
- if record.employee_id and not record.holiday_status_id.limit:
- leaves_rest = holi_status_obj.get_days( cr, uid, [record.holiday_status_id.id], record.employee_id.id, False)[record.holiday_status_id.id]['remaining_leaves']
- if leaves_rest < record.number_of_days_temp:
- raise osv.except_osv(_('Warning!'), _('There are not enough %s allocated for employee %s; please create an allocation request for this leave type.') % (record.holiday_status_id.name, record.employee_id.name))
+ for record in self.browse(cr, uid, ids, context=context):
+ if record.holiday_type != 'employee' or record.type != 'remove' or not record.employee_id or record.holiday_status_id.limit:
+ continue
+ leave_days = self.pool.get('hr.holidays.status').get_days(cr, uid, [record.holiday_status_id.id], record.employee_id.id, context=context)[record.holiday_status_id.id]
+ if leave_days['remaining_leaves'] < record.number_of_days_temp:
+ raise osv.except_osv(_('Warning!'),
+ _('There are not enough remaining days available in %s for employee %s.') % (record.holiday_status_id.name, record.employee_id.name))
+ if leave_days['virtual_remaining_leaves'] < record.number_of_days_temp:
+ raise osv.except_osv(_('Warning!'),
+ _('Other pending requests already book too much days in %s for employee %s.') % (record.holiday_status_id.name, record.employee_id.name))
return True
# -----------------------------
diff --git a/addons/hr_holidays/hr_holidays_data.xml b/addons/hr_holidays/hr_holidays_data.xml
index 2b370f08821..703fa74a2ee 100644
--- a/addons/hr_holidays/hr_holidays_data.xml
+++ b/addons/hr_holidays/hr_holidays_data.xml
@@ -49,7 +49,7 @@
To Approve
hr.holidays
- Request created and waiting confirmation
+ Request confirmed and waiting approval
Approved
diff --git a/addons/hr_holidays/hr_holidays_view.xml b/addons/hr_holidays/hr_holidays_view.xml
index 85bbcb96f94..d532a8e9bbb 100644
--- a/addons/hr_holidays/hr_holidays_view.xml
+++ b/addons/hr_holidays/hr_holidays_view.xml
@@ -53,11 +53,14 @@
1
diff --git a/addons/hr_holidays/hr_holidays_workflow.xml b/addons/hr_holidays/hr_holidays_workflow.xml
index b729ea54a30..14cbc48138a 100644
--- a/addons/hr_holidays/hr_holidays_workflow.xml
+++ b/addons/hr_holidays/hr_holidays_workflow.xml
@@ -2,15 +2,16 @@
-
@@ -21,13 +22,15 @@
- True
draft
+ function
+ holidays_reset()
confirm
+ True
function
holidays_confirm()
OR
@@ -48,11 +51,9 @@
OR
-
refuse
- True
function
holidays_refuse()
@@ -61,9 +62,20 @@
workflow transition
-->
-
+
+ confirm
+ can_reset
+
+
+
+
+
+
+ reset
+ can_reset
+
@@ -82,7 +94,6 @@
-
@@ -107,7 +118,6 @@
-
@@ -124,5 +134,13 @@
+
+
+
+ reset
+ can_reset
+
+
+
diff --git a/addons/hr_holidays/security/ir.model.access.csv b/addons/hr_holidays/security/ir.model.access.csv
index 0375a17ac53..8a2594bb7d7 100644
--- a/addons/hr_holidays/security/ir.model.access.csv
+++ b/addons/hr_holidays/security/ir.model.access.csv
@@ -1,8 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_hr_holydays_status_user,hr.holidays.status user,model_hr_holidays_status,base.group_hr_user,1,1,1,1
access_hr_holidays_user,hr.holidays.user,model_hr_holidays,base.group_hr_user,1,1,1,1
access_hr_holidays_employee,hr.holidays.employee,model_hr_holidays,base.group_user,1,1,1,1
access_hr_holydays_status_employee,hr.holidays.status employee,model_hr_holidays_status,base.group_user,1,0,0,0
+access_hr_holydays_status_manager,hr.holidays.status manager,model_hr_holidays_status,base.group_hr_manager,1,1,1,1
access_hr_holidays_remain_user,hr.holidays.ramain.user,model_hr_holidays_remaining_leaves_user,base.group_hr_user,1,1,1,1
access_resource_calendar_leaves_user,resource_calendar_leaves_user,resource.model_resource_calendar_leaves,base.group_hr_user,1,1,1,1
+access_crm_meeting_hr_user,crm.meeting.hr.user,base_calendar.model_crm_meeting,base.group_hr_user,1,1,1,1
access_crm_meeting_type_manager,crm.meeting.type.manager,base_calendar.model_crm_meeting_type,base.group_hr_manager,1,1,1,1
diff --git a/addons/hr_holidays/test/test_hr_holiday.yml b/addons/hr_holidays/test/test_hr_holiday.yml
index 2bb60300cff..7bedd3302e8 100644
--- a/addons/hr_holidays/test/test_hr_holiday.yml
+++ b/addons/hr_holidays/test/test_hr_holiday.yml
@@ -18,7 +18,7 @@
I again set to draft and then confirm.
-
!python {model: hr.holidays}: |
- self.set_to_draft(cr, uid, [ref('hr_holidays_employee1_cl')])
+ self.holidays_reset(cr, uid, [ref('hr_holidays_employee1_cl')])
self.signal_confirm(cr, uid, [ref('hr_holidays_employee1_cl')])
-
I validate the holiday request by clicking on "To Approve" button.
diff --git a/addons/hr_holidays/tests/__init__.py b/addons/hr_holidays/tests/__init__.py
new file mode 100644
index 00000000000..f70a6277163
--- /dev/null
+++ b/addons/hr_holidays/tests/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.hr_holidays.tests import test_holidays_flow
+
+checks = [
+ test_holidays_flow,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/hr_holidays/tests/common.py b/addons/hr_holidays/tests/common.py
new file mode 100644
index 00000000000..2124a25ac39
--- /dev/null
+++ b/addons/hr_holidays/tests/common.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.tests import common
+
+
+class TestHrHolidaysBase(common.TransactionCase):
+
+ def setUp(self):
+ super(TestHrHolidaysBase, self).setUp()
+ cr, uid = self.cr, self.uid
+
+ # Usefull models
+ self.hr_employee = self.registry('hr.employee')
+ self.hr_holidays = self.registry('hr.holidays')
+ self.hr_holidays_status = self.registry('hr.holidays.status')
+ 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 Hr User group
+ group_hr_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_user')
+ self.group_hr_user_ref_id = group_hr_user_ref and group_hr_user_ref[1] or False
+
+ # Find Hr Manager group
+ group_hr_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager')
+ self.group_hr_manager_ref_id = group_hr_manager_ref and group_hr_manager_ref[1] or False
+
+ # Test partners to use through the various tests
+ self.hr_partner_id = self.res_partner.create(cr, uid, {
+ 'name': 'Gertrude AgrolaitPartner',
+ 'email': 'gertrude.partner@agrolait.com',
+ })
+ self.email_partner_id = self.res_partner.create(cr, uid, {
+ 'name': 'Patrick Ratatouille',
+ 'email': 'patrick.ratatouille@agrolait.com',
+ })
+
+ # Test users to use through the various tests
+ self.user_hruser_id = self.res_users.create(cr, uid, {
+ 'name': 'Armande HrUser',
+ 'login': 'Armande',
+ 'alias_name': 'armande',
+ 'email': 'armande.hruser@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_hr_user_ref_id])]
+ }, {'no_reset_password': True})
+ self.user_hrmanager_id = self.res_users.create(cr, uid, {
+ 'name': 'Bastien HrManager',
+ 'login': 'bastien',
+ 'alias_name': 'bastien',
+ 'email': 'bastien.hrmanager@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_hr_manager_ref_id])]
+ }, {'no_reset_password': True})
+ self.user_none_id = self.res_users.create(cr, uid, {
+ 'name': 'Charlie Avotbonkeur',
+ 'login': 'charlie',
+ 'alias_name': 'charlie',
+ 'email': 'charlie.noone@example.com',
+ 'groups_id': [(6, 0, [])]
+ }, {'no_reset_password': True})
+ self.user_employee_id = self.res_users.create(cr, uid, {
+ 'name': 'David Employee',
+ 'login': 'david',
+ 'alias_name': 'david',
+ 'email': 'david.employee@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id])]
+ }, {'no_reset_password': True})
+
+ # Hr Data
+ self.employee_emp_id = self.hr_employee.create(cr, uid, {
+ 'name': 'David Employee',
+ 'user_id': self.user_employee_id,
+ })
+ self.employee_hruser_id = self.hr_employee.create(cr, uid, {
+ 'name': 'Armande HrUser',
+ 'user_id': self.user_hruser_id,
+ })
diff --git a/addons/hr_holidays/tests/test_holidays_flow.py b/addons/hr_holidays/tests/test_holidays_flow.py
new file mode 100644
index 00000000000..caa6e14f2db
--- /dev/null
+++ b/addons/hr_holidays/tests/test_holidays_flow.py
@@ -0,0 +1,207 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+from openerp.addons.hr_holidays.tests.common import TestHrHolidaysBase
+from openerp.osv.orm import except_orm
+from openerp.tools import mute_logger
+
+
+class TestHolidaysFlow(TestHrHolidaysBase):
+
+ @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
+ def test_00_leave_request_flow(self):
+ """ Testing leave request flow """
+ cr, uid = self.cr, self.uid
+
+ def _check_holidays_status(holiday_status, ml, lt, rl, vrl):
+ self.assertEqual(holiday_status.max_leaves, ml,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.leaves_taken, lt,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.remaining_leaves, rl,
+ 'hr_holidays: wrong type days computation')
+ self.assertEqual(holiday_status.virtual_remaining_leaves, vrl,
+ 'hr_holidays: wrong type days computation')
+
+ # HrUser creates some holiday statuses -> crash because only HrManagers should do this
+ with self.assertRaises(except_orm):
+ self.holidays_status_dummy = self.hr_holidays_status.create(cr, self.user_hruser_id, {
+ 'name': 'UserCheats',
+ 'limit': True,
+ })
+
+ # HrManager creates some holiday statuses
+ self.holidays_status_0 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
+ 'name': 'WithMeetingType',
+ 'limit': True,
+ 'categ_id': self.registry('crm.meeting.type').create(cr, self.user_hrmanager_id, {'name': 'NotLimitedMeetingType'}),
+ })
+ self.holidays_status_1 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
+ 'name': 'NotLimited',
+ 'limit': True,
+ })
+ self.holidays_status_2 = self.hr_holidays_status.create(cr, self.user_hrmanager_id, {
+ 'name': 'Limited',
+ 'limit': False,
+ 'double_validation': True,
+ })
+
+ # --------------------------------------------------
+ # Case1: unlimited type of leave request
+ # --------------------------------------------------
+
+ # Employee creates a leave request for another employee -> should crash
+ with self.assertRaises(except_orm):
+ self.hr_holidays.create(cr, self.user_employee_id, {
+ 'name': 'Hol10',
+ 'employee_id': self.employee_hruser_id,
+ 'holiday_status_id': self.holidays_status_1,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days_temp': 1,
+ })
+
+ # Employee creates a leave request in a no-limit category
+ hol1_id = self.hr_holidays.create(cr, self.user_employee_id, {
+ 'name': 'Hol11',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_1,
+ 'date_from': (datetime.today() - relativedelta(days=1)),
+ 'date_to': datetime.today(),
+ 'number_of_days_temp': 1,
+ })
+ hol1 = self.hr_holidays.browse(cr, self.user_hruser_id, hol1_id)
+ self.assertEqual(hol1.state, 'confirm', 'hr_holidays: newly created leave request should be in confirm state')
+
+ # Employee validates its leave request -> should not work
+ self.hr_holidays.signal_validate(cr, self.user_employee_id, [hol1_id])
+ hol1.refresh()
+ self.assertEqual(hol1.state, 'confirm', 'hr_holidays: employee should not be able to validate its own leave request')
+
+ # HrUser validates the employee leave request
+ self.hr_holidays.signal_validate(cr, self.user_hrmanager_id, [hol1_id])
+ hol1.refresh()
+ self.assertEqual(hol1.state, 'validate', 'hr_holidays: validates leave request should be in validate state')
+
+ # --------------------------------------------------
+ # Case2: limited type of leave request
+ # --------------------------------------------------
+
+ # Employee creates a new leave request at the same time -> crash, avoid interlapping
+ with self.assertRaises(except_orm):
+ self.hr_holidays.create(cr, self.user_employee_id, {
+ 'name': 'Hol21',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_1,
+ 'date_from': (datetime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': datetime.today(),
+ 'number_of_days_temp': 1,
+ })
+
+ # Employee creates a leave request in a limited category -> crash, not enough days left
+ with self.assertRaises(except_orm):
+ self.hr_holidays.create(cr, self.user_employee_id, {
+ 'name': 'Hol22',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_2,
+ 'date_from': (datetime.today() + relativedelta(days=0)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': (datetime.today() + relativedelta(days=1)),
+ 'number_of_days_temp': 1,
+ })
+
+ # Clean transaction
+ self.hr_holidays.unlink(cr, uid, self.hr_holidays.search(cr, uid, [('name', 'in', ['Hol21', 'Hol22'])]))
+
+ # HrUser allocates some leaves to the employee
+ aloc1_id = self.hr_holidays.create(cr, self.user_hruser_id, {
+ 'name': 'Days for limited category',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_2,
+ 'type': 'add',
+ 'number_of_days_temp': 2,
+ })
+ # HrUser validates the allocation request
+ self.hr_holidays.signal_validate(cr, self.user_hruser_id, [aloc1_id])
+ self.hr_holidays.signal_second_validate(cr, self.user_hruser_id, [aloc1_id])
+ # Checks Employee has effectively some days left
+ hol_status_2 = self.hr_holidays_status.browse(cr, self.user_employee_id, self.holidays_status_2)
+ _check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 2.0)
+
+ # Employee creates a leave request in the limited category, now that he has some days left
+ hol2_id = self.hr_holidays.create(cr, self.user_employee_id, {
+ 'name': 'Hol22',
+ 'employee_id': self.employee_emp_id,
+ 'holiday_status_id': self.holidays_status_2,
+ 'date_from': (datetime.today() + relativedelta(days=2)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': (datetime.today() + relativedelta(days=3)),
+ 'number_of_days_temp': 1,
+ })
+ hol2 = self.hr_holidays.browse(cr, self.user_hruser_id, hol2_id)
+ # Check left days: - 1 virtual remaining day
+ hol_status_2.refresh()
+ _check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 1.0)
+
+ # HrUser validates the first step
+ self.hr_holidays.signal_validate(cr, self.user_hruser_id, [hol2_id])
+ hol2.refresh()
+ self.assertEqual(hol2.state, 'validate1',
+ 'hr_holidays: first validation should lead to validate1 state')
+
+ # HrUser validates the second step
+ self.hr_holidays.signal_second_validate(cr, self.user_hruser_id, [hol2_id])
+ hol2.refresh()
+ self.assertEqual(hol2.state, 'validate',
+ 'hr_holidays: second validation should lead to validate state')
+ # Check left days: - 1 day taken
+ hol_status_2.refresh()
+ _check_holidays_status(hol_status_2, 2.0, 1.0, 1.0, 1.0)
+
+ # HrManager finds an error: he refuses the leave request
+ self.hr_holidays.signal_refuse(cr, self.user_hrmanager_id, [hol2_id])
+ hol2.refresh()
+ self.assertEqual(hol2.state, 'refuse',
+ 'hr_holidays: refuse should lead to refuse state')
+ # Check left days: 2 days left again
+ hol_status_2.refresh()
+ _check_holidays_status(hol_status_2, 2.0, 0.0, 2.0, 2.0)
+
+ # Annoyed, HrUser tries to fix its error and tries to reset the leave request -> does not work, only HrManager
+ self.hr_holidays.signal_reset(cr, self.user_hruser_id, [hol2_id])
+ self.assertEqual(hol2.state, 'refuse',
+ 'hr_holidays: hr_user should not be able to reset a refused leave request')
+
+ # HrManager resets the request
+ self.hr_holidays.signal_reset(cr, self.user_hrmanager_id, [hol2_id])
+ hol2.refresh()
+ self.assertEqual(hol2.state, 'draft',
+ 'hr_holidays: resetting should lead to draft state')
+
+ # HrManager changes the date and put too much days -> crash when confirming
+ self.hr_holidays.write(cr, self.user_hrmanager_id, [hol2_id], {
+ 'date_from': (datetime.today() + relativedelta(days=4)).strftime('%Y-%m-%d %H:%M'),
+ 'date_to': (datetime.today() + relativedelta(days=7)),
+ 'number_of_days_temp': 4,
+ })
+ with self.assertRaises(except_orm):
+ self.hr_holidays.signal_confirm(cr, self.user_hrmanager_id, [hol2_id])
diff --git a/addons/hr_timesheet/test/test_hr_timesheet.yml b/addons/hr_timesheet/test/test_hr_timesheet.yml
index 91219863e34..01687dfe78a 100644
--- a/addons/hr_timesheet/test/test_hr_timesheet.yml
+++ b/addons/hr_timesheet/test/test_hr_timesheet.yml
@@ -35,7 +35,8 @@
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': 'Gilles Gravie', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'action'})
+ 'name': 'Gilles Gravie', '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 Project
hr.sign.in.project
form
tree,form
@@ -67,7 +67,7 @@
-
+
@@ -82,7 +82,7 @@
- Sign in / Sign out by project
+ Sign in / Sign out by Project
hr.sign.out.project
form
tree,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/__openerp__.py b/addons/mail/__openerp__.py
index 8f072dd9082..1b9111d3040 100644
--- a/addons/mail/__openerp__.py
+++ b/addons/mail/__openerp__.py
@@ -83,7 +83,6 @@ Main Features
'static/src/css/mail_group.css',
],
'js': [
- 'static/lib/jquery.expander/jquery.expander.js',
'static/src/js/mail.js',
'static/src/js/mail_followers.js',
'static/src/js/many2many_tags_email.js',
diff --git a/addons/mail/doc/changelog.rst b/addons/mail/doc/changelog.rst
new file mode 100644
index 00000000000..a7a2cfcf2b5
--- /dev/null
+++ b/addons/mail/doc/changelog.rst
@@ -0,0 +1,17 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+ - added support of ``active_domain`` form context, coming from the list view.
+ When checking the header hook, the mass mailing will be done on all records
+ matching the ``active_domain``.
+ - added ``mail_server_id`` to mail_message, removing it from mail_mail. This allows
+ to set the preferred mail server to use for notifications emails, when using
+ templates.
+ - added ``_mail_post_access`` attribute that specifies the access right that
+ should have the user in order to post a new message on a given model. Values
+ are ``read`` (portal documents), ``write`` (default value), ``unlink`` or ``create``.
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/i18n/bs.po b/addons/mail/i18n/bs.po
new file mode 100644
index 00000000000..b50a043e2a7
--- /dev/null
+++ b/addons/mail/i18n/bs.po
@@ -0,0 +1,1687 @@
+# Bosnian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:04+0000\n"
+"PO-Revision-Date: 2013-08-06 10:21+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Bosnian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
+"X-Generator: Launchpad (build 16721)\n"
+
+#. module: mail
+#: view:mail.followers:0
+msgid "Followers Form"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_publisher_warranty_contract
+msgid "publisher_warranty.contract"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,author_id:0
+#: view:mail.mail:0
+#: field:mail.message,author_id:0
+msgid "Author"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Message Details"
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,email_to:0
+msgid "Message recipients"
+msgstr ""
+
+#. module: mail
+#: help:mail.message.subtype,default:0
+msgid "Activated by default when subscribing."
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+msgid "Comments"
+msgstr ""
+
+#. module: mail
+#: view:mail.alias:0
+#: view:mail.mail:0
+msgid "Group By..."
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,body:0
+#: help:mail.message,body:0
+msgid "Automatically sanitized HTML contents"
+msgstr ""
+
+#. module: mail
+#: help:mail.alias,alias_name:0
+msgid ""
+"The name of the email alias, e.g. 'jobs' if you want to catch emails for "
+""
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_email_compose_message_wizard
+#: view:mail.compose.message:0
+msgid "Compose Email"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:132
+#, python-format
+msgid "Add them into recipients and followers"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid "Group Name"
+msgstr ""
+
+#. module: mail
+#: selection:mail.group,public:0
+msgid "Public"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Body"
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+msgid "Show messages to read"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,email_from:0
+#: help:mail.message,email_from:0
+msgid ""
+"Email address of the sender. This field is set when no matching partner is "
+"found for incoming emails."
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_compose_message
+msgid "Email composition wizard"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:23
+#, python-format
+msgid "Add others"
+msgstr ""
+
+#. module: mail
+#: field:mail.message.subtype,parent_id:0
+msgid "Parent"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,message_unread:0
+#: field:mail.thread,message_unread:0
+#: field:res.partner,message_unread:0
+msgid "Unread Messages"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:313
+#, python-format
+msgid "show"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,group_ids:0
+msgid ""
+"Members of those groups will automatically added as followers. Note that "
+"they will be able to manage their subscription manually if necessary."
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/js/mail.js:1029
+#, python-format
+msgid "Do you really want to delete this message?"
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+#: field:mail.notification,read:0
+msgid "Read"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid "Search Groups"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/js/mail_followers.js:157
+#, python-format
+msgid "followers"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/mail_message.py:737
+#, python-format
+msgid "Access Denied"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,image_medium:0
+msgid ""
+"Medium-sized photo of the group. It is automatically resized as a 128x128px "
+"image, with aspect ratio preserved. Use this field in form views or some "
+"kanban views."
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:212
+#, python-format
+msgid "Uploading error"
+msgstr ""
+
+#. module: mail
+#: model:mail.group,name:mail.group_support
+msgid "Support"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/mail_message.py:738
+#, python-format
+msgid ""
+"The requested operation cannot be completed due to security restrictions. "
+"Please contact your system administrator.\n"
+"\n"
+"(Document type: %s, Operation: %s)"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+#: selection:mail.mail,state:0
+msgid "Received"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Thread"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:29
+#, python-format
+msgid "Open the full mail composer"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:29
+#, python-format
+msgid "ò"
+msgstr ""
+
+#. module: mail
+#: field:base.config.settings,alias_domain:0
+msgid "Alias Domain"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,group_ids:0
+msgid "Auto Subscription"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,references:0
+msgid "References"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:206
+#, python-format
+msgid "No messages."
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_group
+msgid "Discussion group"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:96
+#: code:addons/mail/static/src/xml/mail.xml:108
+#, python-format
+msgid "uploading"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:52
+#, python-format
+msgid "more."
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,type:0
+#: help:mail.message,type:0
+msgid ""
+"Message type: email for email message, notification for system message, "
+"comment for other messages such as user replies"
+msgstr ""
+
+#. module: mail
+#: help:mail.message.subtype,relation_field:0
+msgid ""
+"Field used to link the related model to the subtype model when using "
+"automatic subscription on a related document. The field is used to compute "
+"getattr(related_document.relation_field)."
+msgstr ""
+
+#. module: mail
+#: selection:mail.mail,state:0
+msgid "Cancelled"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,reply_to:0
+msgid "Reply-To"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/wizard/invite.py:37
+#, python-format
+msgid "You have been invited to follow %s.
"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,message_unread:0
+#: help:mail.thread,message_unread:0
+#: help:res.partner,message_unread:0
+msgid "If checked new messages require your attention."
+msgstr ""
+
+#. module: mail
+#: field:mail.group,image_medium:0
+msgid "Medium-sized photo"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,name:mail.action_mail_to_me_feeds
+#: model:ir.ui.menu,name:mail.mail_tomefeeds
+msgid "To: me"
+msgstr ""
+
+#. module: mail
+#: field:mail.message.subtype,name:0
+msgid "Message Type"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,auto_delete:0
+msgid "Auto Delete"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:12
+#: view:mail.group:0
+#, python-format
+msgid "Unfollow"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:312
+#, python-format
+msgid "show one more message"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/mail_mail.py:75
+#: code:addons/mail/res_users.py:69
+#, python-format
+msgid "Invalid Action!"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:25
+#, python-format
+msgid "User img"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_view_mail_mail
+#: model:ir.ui.menu,name:mail.menu_mail_mail
+#: view:mail.mail:0
+#: view:mail.message:0
+msgid "Emails"
+msgstr ""
+
+#. module: mail
+#: field:mail.followers,partner_id:0
+msgid "Related Partner"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,message_summary:0
+#: help:mail.thread,message_summary:0
+#: help:res.partner,message_summary:0
+msgid ""
+"Holds the Chatter summary (number of messages, ...). This summary is "
+"directly in html format in order to be inserted in kanban views."
+msgstr ""
+
+#. module: mail
+#: help:mail.alias,alias_model_id:0
+msgid ""
+"The model (OpenERP Document Kind) to which this alias corresponds. Any "
+"incoming email that does not reply to an existing record will cause the "
+"creation of a new record of this model (e.g. a Project Task)"
+msgstr ""
+
+#. module: mail
+#: field:mail.message.subtype,relation_field:0
+msgid "Relation field"
+msgstr ""
+
+#. module: mail
+#: selection:mail.compose.message,type:0
+#: selection:mail.message,type:0
+msgid "System notification"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_res_partner
+msgid "Partner"
+msgstr ""
+
+#. module: mail
+#: model:ir.ui.menu,name:mail.mail_my_stuff
+msgid "Organizer"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,subject:0
+#: field:mail.message,subject:0
+msgid "Subject"
+msgstr ""
+
+#. module: mail
+#: field:mail.wizard.invite,partner_ids:0
+msgid "Partners"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Retry"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,email_from:0
+#: field:mail.mail,email_from:0
+#: field:mail.message,email_from:0
+msgid "From"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+#: view:mail.message.subtype:0
+msgid "Email message"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_base_config_settings
+msgid "base.config.settings"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:36
+#: view:mail.compose.message:0
+#, python-format
+msgid "Send"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/js/mail_followers.js:153
+#, python-format
+msgid "No followers"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Failed"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:22
+#: model:ir.actions.act_window,name:mail.action_view_followers
+#: model:ir.ui.menu,name:mail.menu_email_followers
+#: view:mail.followers:0
+#: field:mail.group,message_follower_ids:0
+#: field:mail.thread,message_follower_ids:0
+#: field:res.partner,message_follower_ids:0
+#, python-format
+msgid "Followers"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,name:mail.action_mail_archives_feeds
+#: model:ir.ui.menu,name:mail.mail_archivesfeeds
+msgid "Archives"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:95
+#: code:addons/mail/static/src/xml/mail.xml:107
+#, python-format
+msgid "Delete this attachment"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:245
+#: view:mail.mail:0
+#, python-format
+msgid "Reply"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/js/mail_followers.js:155
+#, python-format
+msgid "One follower"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,type:0
+#: field:mail.message,type:0
+msgid "Type"
+msgstr ""
+
+#. module: mail
+#: selection:mail.compose.message,type:0
+#: view:mail.mail:0
+#: selection:mail.message,type:0
+msgid "Email"
+msgstr ""
+
+#. module: mail
+#: field:ir.ui.menu,mail_group_id:0
+msgid "Mail Group"
+msgstr ""
+
+#. module: mail
+#: selection:res.partner,notification_email_send:0
+msgid "Comments and Emails"
+msgstr ""
+
+#. module: mail
+#: field:mail.alias,alias_defaults:0
+msgid "Default Values"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/res_users.py:89
+#, python-format
+msgid "%s has joined the %s network."
+msgstr ""
+
+#. module: mail
+#: help:mail.group,image_small:0
+msgid ""
+"Small-sized photo of the group. It is automatically resized as a 64x64px "
+"image, with aspect ratio preserved. Use this field anywhere a small image is "
+"required."
+msgstr ""
+
+#. module: mail
+#: view:mail.compose.message:0
+#: field:mail.message,partner_ids:0
+msgid "Recipients"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:140
+#, python-format
+msgid "<<<"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:43
+#, python-format
+msgid "Write to the followers of this document..."
+msgstr ""
+
+#. module: mail
+#: field:mail.group,group_public_id:0
+msgid "Authorized Group"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid "Join Group"
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,email_from:0
+msgid "Message sender, taken from user preferences."
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/wizard/invite.py:40
+#, python-format
+msgid "You have been invited to follow a new document.
"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,parent_id:0
+#: field:mail.message,parent_id:0
+msgid "Parent Message"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,res_id:0
+#: field:mail.followers,res_id:0
+#: field:mail.message,res_id:0
+#: field:mail.wizard.invite,res_id:0
+msgid "Related Document ID"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,help:mail.action_mail_to_me_feeds
+msgid ""
+"\n"
+" No private message. \n"
+"
\n"
+" This list contains messages sent to you.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: mail
+#: model:mail.group,name:mail.group_rd
+msgid "R&D"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:61
+#, python-format
+msgid "/web/binary/upload_attachment"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_thread
+msgid "Email Thread"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Advanced"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:244
+#, python-format
+msgid "Move to Inbox"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/wizard/mail_compose_message.py:193
+#, python-format
+msgid "Re:"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,to_read:0
+#: field:mail.message,to_read:0
+msgid "To read"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/res_users.py:69
+#, python-format
+msgid ""
+"You may not create a user. To create new users, you should use the "
+"\"Settings > Users\" menu."
+msgstr ""
+
+#. module: mail
+#: help:mail.followers,res_model:0
+#: help:mail.wizard.invite,res_model:0
+msgid "Model of the followed resource"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:337
+#, python-format
+msgid "like"
+msgstr ""
+
+#. module: mail
+#: view:mail.compose.message:0
+#: view:mail.wizard.invite:0
+msgid "Cancel"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:47
+#, python-format
+msgid "Share with my followers..."
+msgstr ""
+
+#. module: mail
+#: field:mail.notification,partner_id:0
+msgid "Contact"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid ""
+"Only the invited followers can read the\n"
+" discussions on this group."
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_ir_ui_menu
+msgid "ir.ui.menu"
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+msgid "Has attachments"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "on"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/mail_message.py:926
+#, python-format
+msgid ""
+"The following partners chosen as recipients for the email have no email "
+"address linked :"
+msgstr ""
+
+#. module: mail
+#: help:mail.alias,alias_defaults:0
+msgid ""
+"A Python dictionary that will be evaluated to provide default values when "
+"creating new records for this alias."
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_message_subtype
+msgid "Message subtypes"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,notified_partner_ids:0
+#: help:mail.message,notified_partner_ids:0
+msgid ""
+"Partners that have a notification pushing this message in their mailboxes"
+msgstr ""
+
+#. module: mail
+#: selection:mail.compose.message,type:0
+#: view:mail.mail:0
+#: selection:mail.message,type:0
+msgid "Comment"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,help:mail.action_mail_inbox_feeds
+msgid ""
+"\n"
+" Good Job! Your inbox is empty.\n"
+"
\n"
+" Your inbox contains private messages or emails sent to "
+"you\n"
+" as well as information related to documents or people "
+"you\n"
+" follow.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,notification:0
+msgid "Is Notification"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:188
+#, python-format
+msgid "Compose a new message"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Send Now"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/mail_mail.py:75
+#, python-format
+msgid ""
+"Unable to send email, please configure the sender's email address or alias."
+msgstr ""
+
+#. module: mail
+#: help:res.users,alias_id:0
+msgid ""
+"Email address internally associated with this user. Incoming emails will "
+"appear in the user's notifications."
+msgstr ""
+
+#. module: mail
+#: field:mail.group,image:0
+msgid "Photo"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:54
+#: code:addons/mail/static/src/xml/mail.xml:191
+#: view:mail.compose.message:0
+#: view:mail.wizard.invite:0
+#, python-format
+msgid "or"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,vote_user_ids:0
+#: help:mail.message,vote_user_ids:0
+msgid "Users that voted for this message"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,alias_id:0
+msgid ""
+"The email address associated with this group. New emails received will "
+"automatically create new topics."
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Month"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Email Search"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,child_ids:0
+#: field:mail.message,child_ids:0
+msgid "Child Messages"
+msgstr ""
+
+#. module: mail
+#: field:mail.alias,alias_user_id:0
+msgid "Owner"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_res_users
+msgid "Users"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_message
+#: field:mail.mail,mail_message_id:0
+#: view:mail.message:0
+#: field:mail.notification,message_id:0
+#: field:mail.wizard.invite,message:0
+msgid "Message"
+msgstr ""
+
+#. module: mail
+#: help:mail.followers,res_id:0
+#: help:mail.wizard.invite,res_id:0
+msgid "Id of the followed resource"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,body:0
+#: field:mail.message,body:0
+msgid "Contents"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_view_mail_alias
+#: model:ir.ui.menu,name:mail.mail_alias_menu
+msgid "Aliases"
+msgstr ""
+
+#. module: mail
+#: help:mail.message.subtype,description:0
+msgid ""
+"Description that will be added in the message posted for this subtype. If "
+"void, the name will be added instead."
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,vote_user_ids:0
+#: field:mail.message,vote_user_ids:0
+msgid "Votes"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid "Group"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,starred:0
+#: help:mail.message,starred:0
+msgid "Current user has a starred notification linked to this message"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,public:0
+msgid "Privacy"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Notification"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/js/mail.js:654
+#, python-format
+msgid "Please complete partner's informations"
+msgstr ""
+
+#. module: mail
+#: view:mail.wizard.invite:0
+msgid "Add Followers"
+msgstr ""
+
+#. module: mail
+#: view:mail.compose.message:0
+msgid "Followers of selected items and"
+msgstr ""
+
+#. module: mail
+#: field:mail.alias,alias_force_thread_id:0
+msgid "Record Thread ID"
+msgstr ""
+
+#. module: mail
+#: model:ir.ui.menu,name:mail.mail_group_root
+msgid "My Groups"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,help:mail.action_mail_archives_feeds
+msgid ""
+"\n"
+" No message found and no message sent yet.\n"
+"
\n"
+" Click on the top-right icon to compose a message. This\n"
+" message will be sent by email if it's an internal "
+"contact.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+#: field:mail.mail,state:0
+msgid "Status"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+#: selection:mail.mail,state:0
+msgid "Outgoing"
+msgstr ""
+
+#. module: mail
+#: selection:res.partner,notification_email_send:0
+msgid "All feeds"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,record_name:0
+#: help:mail.message,record_name:0
+msgid "Name get of the related document."
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_view_notifications
+#: model:ir.model,name:mail.model_mail_notification
+#: model:ir.ui.menu,name:mail.menu_email_notifications
+#: field:mail.compose.message,notification_ids:0
+#: view:mail.message:0
+#: field:mail.message,notification_ids:0
+#: view:mail.notification:0
+msgid "Notifications"
+msgstr ""
+
+#. module: mail
+#: view:mail.alias:0
+msgid "Search Alias"
+msgstr ""
+
+#. module: mail
+#: help:mail.alias,alias_force_thread_id:0
+msgid ""
+"Optional ID of a thread (record) to which all incoming messages will be "
+"attached, even if they did not reply to it. If set, this will disable the "
+"creation of new records completely."
+msgstr ""
+
+#. module: mail
+#: help:mail.message.subtype,name:0
+msgid ""
+"Message subtype gives a more precise type on the message, especially for "
+"system notifications. For example, it can be a notification related to a new "
+"record (New), or to a stage change in a process (Stage change). Message "
+"subtypes allow to precisely tune the notifications the user want to receive "
+"on its wall."
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "by"
+msgstr ""
+
+#. module: mail
+#: model:mail.group,name:mail.group_best_sales_practices
+msgid "Best Sales Practices"
+msgstr ""
+
+#. module: mail
+#: selection:mail.group,public:0
+msgid "Selected Group Only"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,message_is_follower:0
+#: field:mail.thread,message_is_follower:0
+#: field:res.partner,message_is_follower:0
+msgid "Is a Follower"
+msgstr ""
+
+#. module: mail
+#: view:mail.alias:0
+#: view:mail.mail:0
+msgid "User"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid "Groups"
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+msgid "Messages Search"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,date:0
+#: field:mail.message,date:0
+msgid "Date"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:34
+#, python-format
+msgid "Post"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Extended Filters..."
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:120
+#, python-format
+msgid "To:"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:193
+#, python-format
+msgid "Write to my followers"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_res_groups
+msgid "Access Groups"
+msgstr ""
+
+#. module: mail
+#: field:mail.message.subtype,default:0
+msgid "Default"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:311
+#, python-format
+msgid "show more message"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:246
+#, python-format
+msgid "Mark as Todo"
+msgstr ""
+
+#. module: mail
+#: help:mail.message.subtype,parent_id:0
+msgid "Parent subtype, used for automatic subscription."
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_wizard_invite
+msgid "Invite wizard"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,message_summary:0
+#: field:mail.thread,message_summary:0
+#: field:res.partner,message_summary:0
+msgid "Summary"
+msgstr ""
+
+#. module: mail
+#: help:mail.message.subtype,res_model:0
+msgid ""
+"Model the subtype applies to. If False, this subtype applies to all models."
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,subtype_id:0
+#: field:mail.followers,subtype_ids:0
+#: field:mail.message,subtype_id:0
+#: view:mail.message.subtype:0
+msgid "Subtype"
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid "Group Form"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,starred:0
+#: field:mail.message,starred:0
+#: field:mail.notification,starred:0
+msgid "Starred"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:313
+#, python-format
+msgid "more messages"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/update.py:93
+#, python-format
+msgid "Error"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:13
+#, python-format
+msgid "Following"
+msgstr ""
+
+#. module: mail
+#: sql_constraint:mail.alias:0
+msgid ""
+"Unfortunately this email alias is already used, please choose a unique one"
+msgstr ""
+
+#. module: mail
+#: help:mail.alias,alias_user_id:0
+msgid ""
+"The owner of records created upon receiving emails on this alias. If this "
+"field is not set the system will attempt to find the right owner based on "
+"the sender (From) address, or will use the Administrator account if no "
+"system user is found for that address."
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:52
+#, python-format
+msgid "And"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,message_id:0
+#: field:mail.message,message_id:0
+msgid "Message-Id"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,image:0
+msgid ""
+"This field holds the image used as photo for the group, limited to "
+"1024x1024px."
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,attachment_ids:0
+#: view:mail.mail:0
+#: field:mail.message,attachment_ids:0
+msgid "Attachments"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,record_name:0
+#: field:mail.message,record_name:0
+msgid "Message Record Name"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,email_cc:0
+msgid "Cc"
+msgstr ""
+
+#. module: mail
+#: help:mail.notification,starred:0
+msgid "Starred message that goes into the todo mailbox"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:123
+#: view:mail.compose.message:0
+#, python-format
+msgid "Followers of"
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,auto_delete:0
+msgid "Permanently delete this email after sending it, to save space"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,name:mail.action_mail_group_feeds
+msgid "Discussion Group"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:242
+#, python-format
+msgid "Done"
+msgstr ""
+
+#. module: mail
+#: model:mail.message.subtype,name:mail.mt_comment
+msgid "Discussions"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:11
+#, python-format
+msgid "Follow"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,name:0
+msgid "Name"
+msgstr ""
+
+#. module: mail
+#: model:mail.group,name:mail.group_all_employees
+msgid "Whole Company"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:129
+#: code:addons/mail/static/src/xml/mail.xml:292
+#: view:mail.compose.message:0
+#, python-format
+msgid "and"
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,body_html:0
+msgid "Rich-text/HTML message"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+msgid "Creation Month"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:323
+#, python-format
+msgid "Compose new Message"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,menu_id:0
+msgid "Related Menu"
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+msgid "Content"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,email_to:0
+msgid "To"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,notified_partner_ids:0
+#: field:mail.message,notified_partner_ids:0
+msgid "Notified partners"
+msgstr ""
+
+#. module: mail
+#: help:mail.group,public:0
+msgid ""
+"This group is visible by non members. Invisible groups can add "
+"members through the invite button."
+msgstr ""
+
+#. module: mail
+#: model:mail.group,name:mail.group_board
+msgid "Board meetings"
+msgstr ""
+
+#. module: mail
+#: constraint:mail.alias:0
+msgid ""
+"Invalid expression, it must be a literal python dictionary definition e.g. "
+"\"{'field': 'value'}\""
+msgstr ""
+
+#. module: mail
+#: field:mail.alias,alias_model_id:0
+msgid "Aliased Model"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,message_id:0
+#: help:mail.message,message_id:0
+msgid "Message unique identifier"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,description:0
+#: field:mail.message.subtype,description:0
+msgid "Description"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_followers
+msgid "Document Followers"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail_followers.xml:35
+#, python-format
+msgid "Remove this follower"
+msgstr ""
+
+#. module: mail
+#: selection:res.partner,notification_email_send:0
+msgid "Never"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,mail_server_id:0
+msgid "Outgoing mail server"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/mail_message.py:930
+#, python-format
+msgid "Partners email addresses not found"
+msgstr ""
+
+#. module: mail
+#: view:mail.mail:0
+#: selection:mail.mail,state:0
+msgid "Sent"
+msgstr ""
+
+#. module: mail
+#: field:mail.mail,body_html:0
+msgid "Rich-text Contents"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,to_read:0
+#: help:mail.message,to_read:0
+msgid "Current user has an unread notification linked to this message"
+msgstr ""
+
+#. module: mail
+#: help:res.partner,notification_email_send:0
+msgid ""
+"Choose in which case you want to receive an email when you receive new feeds."
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_view_groups
+#: model:ir.ui.menu,name:mail.mail_allgroups
+msgid "Join a group"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,help:mail.action_mail_group_feeds
+msgid ""
+"\n"
+" No message in this group.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:213
+#, python-format
+msgid "Please, wait while the file is uploading."
+msgstr ""
+
+#. module: mail
+#: view:mail.group:0
+msgid ""
+"This group is visible by everyone,\n"
+" including your customers if you "
+"installed\n"
+" the portal module."
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:243
+#, python-format
+msgid "Set back to Todo"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:126
+#, python-format
+msgid "this document"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,filter_id:0
+msgid "Filters"
+msgstr ""
+
+#. module: mail
+#: field:res.partner,notification_email_send:0
+msgid "Receive Feeds by Email"
+msgstr ""
+
+#. module: mail
+#: help:base.config.settings,alias_domain:0
+msgid ""
+"If you have setup a catch-all email domain redirected to the OpenERP server, "
+"enter the domain name here."
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_view_mail_message
+#: model:ir.ui.menu,name:mail.menu_mail_message
+#: field:mail.group,message_ids:0
+#: view:mail.message:0
+#: field:mail.thread,message_ids:0
+#: field:res.partner,message_ids:0
+msgid "Messages"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:139
+#, python-format
+msgid "others..."
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,name:mail.action_mail_star_feeds
+#: model:ir.ui.menu,name:mail.mail_starfeeds
+msgid "To-do"
+msgstr ""
+
+#. module: mail
+#: view:mail.alias:0
+#: field:mail.alias,alias_name:0
+#: field:mail.group,alias_id:0
+#: field:res.users,alias_id:0
+msgid "Alias"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_mail
+msgid "Outgoing Mails"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,notification_ids:0
+#: help:mail.message,notification_ids:0
+msgid ""
+"Technical field holding the message notifications. Use notified_partner_ids "
+"to access notified partners."
+msgstr ""
+
+#. module: mail
+#: model:ir.ui.menu,name:mail.mail_feeds
+#: model:ir.ui.menu,name:mail.mail_feeds_main
+msgid "Messaging"
+msgstr ""
+
+#. module: mail
+#: view:mail.alias:0
+#: field:mail.message.subtype,res_model:0
+msgid "Model"
+msgstr ""
+
+#. module: mail
+#: view:mail.message:0
+msgid "Unread"
+msgstr ""
+
+#. module: mail
+#: help:mail.followers,subtype_ids:0
+msgid ""
+"Message subtypes followed, meaning subtypes that will be pushed onto the "
+"user's Wall."
+msgstr ""
+
+#. module: mail
+#: help:mail.group,message_ids:0
+#: help:mail.thread,message_ids:0
+#: help:res.partner,message_ids:0
+msgid "Messages and communication history"
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,references:0
+msgid "Message references, such as identifiers of previous messages"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,composition_mode:0
+msgid "Composition mode"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,model:0
+#: field:mail.followers,res_model:0
+#: field:mail.message,model:0
+#: field:mail.wizard.invite,res_model:0
+msgid "Related Document Model"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:338
+#, python-format
+msgid "unlike"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,author_id:0
+#: help:mail.message,author_id:0
+msgid ""
+"Author of the message. If not set, email_from may hold an email address that "
+"did not match any partner."
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,email_cc:0
+msgid "Carbon copy message recipients"
+msgstr ""
+
+#. module: mail
+#: field:mail.alias,alias_domain:0
+msgid "Alias domain"
+msgstr ""
+
+#. module: mail
+#: code:addons/mail/update.py:93
+#, python-format
+msgid "Error during communication with the publisher warranty server."
+msgstr ""
+
+#. module: mail
+#: selection:mail.group,public:0
+msgid "Private"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,help:mail.action_mail_star_feeds
+msgid ""
+"\n"
+" No todo. \n"
+"
\n"
+" When you process messages in your inbox, you can mark "
+"some\n"
+" as todo . From this menu, you can process all your "
+"todo.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: mail
+#: selection:mail.mail,state:0
+msgid "Delivery Failed"
+msgstr ""
+
+#. module: mail
+#: field:mail.compose.message,partner_ids:0
+msgid "Additional contacts"
+msgstr ""
+
+#. module: mail
+#: help:mail.compose.message,parent_id:0
+#: help:mail.message,parent_id:0
+msgid "Initial thread message."
+msgstr ""
+
+#. module: mail
+#: model:mail.group,name:mail.group_hr_policies
+msgid "HR Policies"
+msgstr ""
+
+#. module: mail
+#: selection:res.partner,notification_email_send:0
+msgid "Emails only"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.client,name:mail.action_mail_inbox_feeds
+#: model:ir.ui.menu,name:mail.mail_inboxfeeds
+msgid "Inbox"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/xml/mail.xml:58
+#, python-format
+msgid "File"
+msgstr ""
+
+#. module: mail
+#. openerp-web
+#: code:addons/mail/static/src/js/many2many_tags_email.js:63
+#, python-format
+msgid "Please complete partner's informations and Email"
+msgstr ""
+
+#. module: mail
+#: model:ir.actions.act_window,name:mail.action_view_message_subtype
+#: model:ir.ui.menu,name:mail.menu_message_subtype
+msgid "Subtypes"
+msgstr ""
+
+#. module: mail
+#: model:ir.model,name:mail.model_mail_alias
+msgid "Email Aliases"
+msgstr ""
+
+#. module: mail
+#: field:mail.group,image_small:0
+msgid "Small-sized photo"
+msgstr ""
+
+#. module: mail
+#: help:mail.mail,reply_to:0
+msgid "Preferred response address for the message"
+msgstr ""
diff --git a/addons/mail/mail_followers.py b/addons/mail/mail_followers.py
index 9d96a6ff59c..ac350810aa6 100644
--- a/addons/mail/mail_followers.py
+++ b/addons/mail/mail_followers.py
@@ -151,7 +151,7 @@ class mail_notification(osv.Model):
return footer
def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None,
- force_send=False, user_signature=True):
+ force_send=False, user_signature=True):
""" Send by email the notification depending on the user preferences
:param list partners_to_notify: optional list of partner ids restricting
diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py
index d45ae04ddd1..b105d6677bd 100644
--- a/addons/mail/mail_mail.py
+++ b/addons/mail/mail_mail.py
@@ -44,7 +44,6 @@ class mail_mail(osv.Model):
_columns = {
'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
- 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
'state': fields.selection([
('outgoing', 'Outgoing'),
('sent', 'Sent'),
@@ -140,10 +139,16 @@ class mail_mail(osv.Model):
# notification field: if not set, set if mail comes from an existing mail.message
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
+ mail_id = super(mail_mail, self).create(cr, uid, values, context=context)
+
# reply_to: if not set, set with default values that require creation values
+ # but delegate after creation because of mail_message.message_id automatic
+ # creation using existence of reply_to
if not values.get('reply_to'):
- values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
- return super(mail_mail, self).create(cr, uid, values, context=context)
+ reply_to = self._get_reply_to(cr, uid, values, context=context)
+ if reply_to:
+ self.write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
+ return mail_id
def unlink(self, cr, uid, ids, context=None):
# cascade-delete the parent message for all mails that are not created for a notification
diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py
index 9d6829f3f6e..f90baa5a320 100644
--- a/addons/mail/mail_message.py
+++ b/addons/mail/mail_message.py
@@ -191,6 +191,7 @@ class mail_message(osv.Model):
'vote_user_ids': fields.many2many('res.users', 'mail_vote',
'message_id', 'user_id', string='Votes',
help='Users that voted for this message'),
+ 'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
}
def _needaction_domain_get(self, cr, uid, context=None):
@@ -394,15 +395,21 @@ class mail_message(osv.Model):
has_voted = uid in [user.id for user in message.vote_user_ids]
try:
- body_html = html_email_clean(message.body)
+ if parent_id:
+ max_length = 300
+ else:
+ max_length = 100
+ body_short = html_email_clean(message.body, remove=False, shorten=True, max_length=max_length)
+
except Exception:
- body_html = 'Encoding Error : Unable to convert this message (id: %s).
' % message.id
+ body_short = 'Encoding Error : Unable to convert this message (id: %s).
' % message.id
_logger.exception(Exception)
return {'id': message.id,
'type': message.type,
'subtype': message.subtype_id.name if message.subtype_id else False,
- 'body': body_html,
+ 'body': message.body,
+ 'body_short': body_short,
'model': message.model,
'res_id': message.res_id,
'record_name': message.record_name,
@@ -780,8 +787,8 @@ class mail_message(osv.Model):
values['message_id'] = tools.generate_tracking_message_id('private')
newid = super(mail_message, self).create(cr, uid, values, context)
self._notify(cr, uid, newid, context=context,
- force_send=context.get('mail_notify_force_send', True),
- user_signature=context.get('mail_notify_user_signature', True))
+ force_send=context.get('mail_notify_force_send', True),
+ user_signature=context.get('mail_notify_user_signature', True))
# TDE FIXME: handle default_starred. Why not setting an inv on starred ?
# Because starred will call set_message_starred, that looks for notifications.
# When creating a new mail_message, it will create a notification to a message
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 2744e6df231..94e9126f020 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
@@ -657,6 +682,12 @@ class mail_thread(osv.AbstractModel):
assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message).'
_warn('posting a message without model should be with a null res_id (private message), resetting thread_id')
thread_id = 0
+ # Private message: should have a parent_id (only answers)
+ if not model and not message_dict.get('parent_id'):
+ if assert_model:
+ assert message_dict.get('parent_id'), 'Routing: posting a message without model should be with a parent_id (private mesage).'
+ _warn('posting a message without model should be with a parent_id (private mesage), skipping')
+ return ()
# Existing Document: check if exists; if not, fallback on create if allowed
if thread_id and not model_pool.exists(cr, uid, thread_id):
@@ -670,7 +701,7 @@ class mail_thread(osv.AbstractModel):
return ()
# Existing Document: check model accepts the mailgateway
- if thread_id and not hasattr(model_pool, 'message_update'):
+ if thread_id and model and not hasattr(model_pool, 'message_update'):
if create_fallback:
_warn('model %s does not accept document update, fall back on document creation' % model)
thread_id = None
@@ -681,7 +712,7 @@ class mail_thread(osv.AbstractModel):
return ()
# New Document: check model accepts the mailgateway
- if not thread_id and not hasattr(model_pool, 'message_new'):
+ if not thread_id and model and not hasattr(model_pool, 'message_new'):
if assert_model:
assert hasattr(model_pool, 'message_new'), 'Model %s does not accept document creation, crashing' % model
_warn('model %s does not accept document creation, skipping' % model)
@@ -773,16 +804,16 @@ class mail_thread(osv.AbstractModel):
# 2. Reply to a private message
if in_reply_to:
- message_ids = self.pool.get('mail.message').search(cr, uid, [
+ mail_message_ids = self.pool.get('mail.message').search(cr, uid, [
('message_id', '=', in_reply_to),
'!', ('message_id', 'ilike', 'reply_to')
], limit=1, context=context)
- if message_ids:
- message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
+ if mail_message_ids:
+ mail_message = self.pool.get('mail.message').browse(cr, uid, mail_message_ids[0], context=context)
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
- email_from, email_to, message_id, message.id, custom_values, uid)
+ email_from, email_to, message_id, mail_message.id, custom_values, uid)
route = self.message_route_verify(cr, uid, message, message_dict,
- (message.model, message.res_id, custom_values, uid, None),
+ (mail_message.model, mail_message.res_id, custom_values, uid, None),
update_author=True, assert_model=True, create_fallback=True, context=context)
return route and [route] or []
@@ -1401,9 +1432,9 @@ class mail_thread(osv.AbstractModel):
# Followers API
#------------------------------------------------------
- def message_get_subscription_data(self, cr, uid, ids, context=None):
+ def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
""" Wrapper to get subtypes data. """
- return self._get_subscription_data(cr, uid, ids, None, None, context=context)
+ return self._get_subscription_data(cr, uid, ids, None, None, user_pid=user_pid, context=context)
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
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/lib/jquery.expander/jquery.expander.js b/addons/mail/static/lib/jquery.expander/jquery.expander.js
deleted file mode 100644
index 91cc03db918..00000000000
--- a/addons/mail/static/lib/jquery.expander/jquery.expander.js
+++ /dev/null
@@ -1,385 +0,0 @@
-/*!
- * jQuery Expander Plugin v1.4.2
- *
- * Date: Fri Mar 16 14:29:56 2012 EDT
- * Requires: jQuery v1.3+
- *
- * Copyright 2011, Karl Swedberg
- * Dual licensed under the MIT and GPL licenses (just like jQuery):
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- *
- *
- *
-*/
-
-(function($) {
- $.expander = {
- version: '1.4.2',
- defaults: {
- // the number of characters at which the contents will be sliced into two parts.
- slicePoint: 100,
-
- // whether to keep the last word of the summary whole (true) or let it slice in the middle of a word (false)
- preserveWords: true,
-
- // a threshold of sorts for whether to initially hide/collapse part of the element's contents.
- // If after slicing the contents in two there are fewer words in the second part than
- // the value set by widow, we won't bother hiding/collapsing anything.
- widow: 4,
-
- // text displayed in a link instead of the hidden part of the element.
- // clicking this will expand/show the hidden/collapsed text
- expandText: 'read more',
- expandPrefix: '… ',
-
- expandAfterSummary: false,
-
- // class names for summary element and detail element
- summaryClass: 'summary',
- detailClass: 'details',
-
- // class names for around "read-more" link and "read-less" link
- moreClass: 'read-more',
- lessClass: 'read-less',
-
- // number of milliseconds after text has been expanded at which to collapse the text again.
- // when 0, no auto-collapsing
- collapseTimer: 0,
-
- // effects for expanding and collapsing
- expandEffect: 'fadeIn',
- expandSpeed: 250,
- collapseEffect: 'fadeOut',
- collapseSpeed: 200,
-
- // allow the user to re-collapse the expanded text.
- userCollapse: true,
-
- // text to use for the link to re-collapse the text
- userCollapseText: 'read less',
- userCollapsePrefix: ' ',
-
-
- // all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called
-
- onSlice: null, // function() {}
- beforeExpand: null, // function() {},
- afterExpand: null, // function() {},
- onCollapse: null // function(byUser) {}
- }
- };
-
- $.fn.expander = function(options) {
- var meth = 'init';
-
- if (typeof options == 'string') {
- meth = options;
- options = {};
- }
-
- var opts = $.extend({}, $.expander.defaults, options),
- rSelfClose = /^<(?:area|br|col|embed|hr|img|input|link|meta|param).*>$/i,
- rAmpWordEnd = opts.wordEnd || /(&(?:[^;]+;)?|[a-zA-Z\u00C0-\u0100]+)$/,
- rOpenCloseTag = /<\/?(\w+)[^>]*>/g,
- rOpenTag = /<(\w+)[^>]*>/g,
- rCloseTag = /<\/(\w+)>/g,
- rLastCloseTag = /(<\/[^>]+>)\s*$/,
- rTagPlus = /^<[^>]+>.?/,
- delayedCollapse;
-
- var methods = {
- init: function() {
- this.each(function() {
- var i, l, tmp, newChar, summTagless, summOpens, summCloses,
- lastCloseTag, detailText, detailTagless,
- $thisDetails, $readMore,
- openTagsForDetails = [],
- closeTagsForsummaryText = [],
- defined = {},
- thisEl = this,
- $this = $(this),
- $summEl = $([]),
- o = $.meta ? $.extend({}, opts, $this.data()) : opts,
- hasDetails = !!$this.find('.' + o.detailClass).length,
- hasBlocks = !!$this.find('*').filter(function() {
- var display = $(this).css('display');
- return (/^block|table|list/).test(display);
- }).length,
- el = hasBlocks ? 'div' : 'span',
- detailSelector = el + '.' + o.detailClass,
- moreSelector = 'span.' + o.moreClass,
- expandSpeed = o.expandSpeed || 0,
- allHtml = $.trim( $this.html() ),
- allText = $.trim( $this.text() ),
- summaryText = allHtml.slice(0, o.slicePoint);
-
- // bail out if we've already set up the expander on this element
- if ( $.data(this, 'expander') ) {
- return;
- }
-
- $.data(this, 'expander', true);
-
- // determine which callback functions are defined
- $.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse'], function(index, val) {
- defined[val] = $.isFunction(o[val]);
- });
-
- // back up if we're in the middle of a tag or word
- summaryText = backup(summaryText);
-
- // summary text sans tags length
- summTagless = summaryText.replace(rOpenCloseTag, '').length;
-
- // add more characters to the summary, one for each character in the tags
- while (summTagless < o.slicePoint) {
- newChar = allHtml.charAt(summaryText.length);
- if (newChar == '<') {
- newChar = allHtml.slice(summaryText.length).match(rTagPlus)[0];
- }
- summaryText += newChar;
- summTagless++;
- }
-
- summaryText = backup(summaryText, o.preserveWords);
-
- // separate open tags from close tags and clean up the lists
- summOpens = summaryText.match(rOpenTag) || [];
- summCloses = summaryText.match(rCloseTag) || [];
-
- // filter out self-closing tags
- tmp = [];
- $.each(summOpens, function(index, val) {
- if ( !rSelfClose.test(val) ) {
- tmp.push(val);
- }
- });
- summOpens = tmp;
-
- // strip close tags to just the tag name
- l = summCloses.length;
- for (i = 0; i < l; i++) {
- summCloses[i] = summCloses[i].replace(rCloseTag, '$1');
- }
-
- // tags that start in summary and end in detail need:
- // a). close tag at end of summary
- // b). open tag at beginning of detail
- $.each(summOpens, function(index, val) {
- var thisTagName = val.replace(rOpenTag, '$1');
- var closePosition = $.inArray(thisTagName, summCloses);
- if (closePosition === -1) {
- openTagsForDetails.push(val);
- closeTagsForsummaryText.push('' + thisTagName + '>');
-
- } else {
- summCloses.splice(closePosition, 1);
- }
- });
-
- // reverse the order of the close tags for the summary so they line up right
- closeTagsForsummaryText.reverse();
-
- // create necessary summary and detail elements if they don't already exist
- if ( !hasDetails ) {
-
- // end script if there is no detail text or if detail has fewer words than widow option
- detailText = allHtml.slice(summaryText.length);
- detailTagless = $.trim( detailText.replace(rOpenCloseTag, '') );
-
- if ( detailTagless === '' || detailTagless.split(/\s+/).length < o.widow ) {
- return;
- }
- // otherwise, continue...
- lastCloseTag = closeTagsForsummaryText.pop() || '';
- summaryText += closeTagsForsummaryText.join('');
- detailText = openTagsForDetails.join('') + detailText;
-
- } else {
- // assume that even if there are details, we still need readMore/readLess/summary elements
- // (we already bailed out earlier when readMore el was found)
- // but we need to create els differently
-
- // remove the detail from the rest of the content
- detailText = $this.find(detailSelector).remove().html();
-
- // The summary is what's left
- summaryText = $this.html();
-
- // allHtml is the summary and detail combined (this is needed when content has block-level elements)
- allHtml = summaryText + detailText;
-
- lastCloseTag = '';
- }
- o.moreLabel = $this.find(moreSelector).length ? '' : buildMoreLabel(o);
-
- if (hasBlocks) {
- detailText = allHtml;
- }
- summaryText += lastCloseTag;
-
- // onSlice callback
- o.summary = summaryText;
- o.details = detailText;
- o.lastCloseTag = lastCloseTag;
-
- if (defined.onSlice) {
- // user can choose to return a modified options object
- // one last chance for user to change the options. sneaky, huh?
- // but could be tricky so use at your own risk.
- tmp = o.onSlice.call(thisEl, o);
-
- // so, if the returned value from the onSlice function is an object with a details property, we'll use that!
- o = tmp && tmp.details ? tmp : o;
- }
-
- // build the html with summary and detail and use it to replace old contents
- var html = buildHTML(o, hasBlocks);
-
- $this.html( html );
-
- // set up details and summary for expanding/collapsing
- $thisDetails = $this.find(detailSelector);
- $readMore = $this.find(moreSelector);
- $thisDetails.hide();
- $readMore.find('a').unbind('click.expander').bind('click.expander', expand);
-
- $summEl = $this.find('div.' + o.summaryClass);
-
- if ( o.userCollapse && !$this.find('span.' + o.lessClass).length ) {
- $this
- .find(detailSelector)
- .append('' + o.userCollapsePrefix + '' + o.userCollapseText + ' ');
- }
-
- $this
- .find('span.' + o.lessClass + ' a')
- .unbind('click.expander')
- .bind('click.expander', function(event) {
- event.preventDefault();
- clearTimeout(delayedCollapse);
- var $detailsCollapsed = $(this).closest(detailSelector);
- reCollapse(o, $detailsCollapsed);
- if (defined.onCollapse) {
- o.onCollapse.call(thisEl, true);
- }
- });
-
- function expand(event) {
- event.preventDefault();
- $readMore.hide();
- $summEl.hide();
- if (defined.beforeExpand) {
- o.beforeExpand.call(thisEl);
- }
-
- $thisDetails.stop(false, true)[o.expandEffect](expandSpeed, function() {
- $thisDetails.css({zoom: ''});
- if (defined.afterExpand) {o.afterExpand.call(thisEl);}
- delayCollapse(o, $thisDetails, thisEl);
- });
- }
-
- }); // this.each
- },
- destroy: function() {
- if ( !this.data('expander') ) {
- return;
- }
- this.removeData('expander');
- this.each(function() {
- var $this = $(this),
- o = $.meta ? $.extend({}, opts, $this.data()) : opts,
- details = $this.find('.' + o.detailClass).contents();
-
- $this.find('.' + o.moreClass).remove();
- $this.find('.' + o.summaryClass).remove();
- $this.find('.' + o.detailClass).after(details).remove();
- $this.find('.' + o.lessClass).remove();
-
- });
- }
- };
-
- // run the methods (almost always "init")
- if ( methods[meth] ) {
- methods[ meth ].call(this);
- }
-
- // utility functions
- function buildHTML(o, blocks) {
- var el = 'span',
- summary = o.summary;
- if ( blocks ) {
- el = 'div';
- // if summary ends with a close tag, tuck the moreLabel inside it
- if ( rLastCloseTag.test(summary) && !o.expandAfterSummary) {
- summary = summary.replace(rLastCloseTag, o.moreLabel + '$1');
- } else {
- // otherwise (e.g. if ends with self-closing tag) just add moreLabel after summary
- // fixes #19
- summary += o.moreLabel;
- }
-
- // and wrap it in a div
- summary = '' + summary + '
';
- } else {
- summary += o.moreLabel;
- }
-
- return [
- summary,
- '<',
- el + ' class="' + o.detailClass + '"',
- '>',
- o.details,
- '' + el + '>'
- ].join('');
- }
-
- function buildMoreLabel(o) {
- var ret = '' + o.expandPrefix;
- ret += '' + o.expandText + ' ';
- return ret;
- }
-
- function backup(txt, preserveWords) {
- if ( txt.lastIndexOf('<') > txt.lastIndexOf('>') ) {
- txt = txt.slice( 0, txt.lastIndexOf('<') );
- }
- if (preserveWords) {
- txt = txt.replace(rAmpWordEnd,'');
- }
-
- return $.trim(txt);
- }
-
- function reCollapse(o, el) {
- el.stop(true, true)[o.collapseEffect](o.collapseSpeed, function() {
- var prevMore = el.prev('span.' + o.moreClass).show();
- if (!prevMore.length) {
- el.parent().children('div.' + o.summaryClass).show()
- .find('span.' + o.moreClass).show();
- }
- });
- }
-
- function delayCollapse(option, $collapseEl, thisEl) {
- if (option.collapseTimer) {
- delayedCollapse = setTimeout(function() {
- reCollapse(option, $collapseEl);
- if ( $.isFunction(option.onCollapse) ) {
- option.onCollapse.call(thisEl, false);
- }
- }, option.collapseTimer);
- }
- }
-
- return this;
- };
-
- // plugin defaults
- $.fn.expander.defaults = $.expander.defaults;
-})(jQuery);
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index 211133afaf8..a4beca607ba 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -118,6 +118,9 @@
text-overflow:ellipsis;
word-wrap: break-word;
}
+.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_body .oe_mail_cleaned {
+ display: none;
+}
/* a) Indented Messages */
@@ -632,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;
@@ -649,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.js b/addons/mail/static/src/js/mail.js
index 5d54a6acc48..62e9241cf4f 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -2,7 +2,7 @@ openerp.mail = function (session) {
var _t = session.web._t,
_lt = session.web._lt;
- var mail = session.mail = {};
+ var mail = session.mail;
openerp_mail_followers(session, mail); // import mail_followers.js
openerp_FieldMany2ManyTagsEmail(session); // import manyy2many_tags_email.js
@@ -225,6 +225,7 @@ openerp.mail = function (session) {
this.name = datasets.name || false,
this.record_name = datasets.record_name || false,
this.body = datasets.body || '',
+ this.body_short = datasets.body_short || '',
this.vote_nb = datasets.vote_nb || 0,
this.has_voted = datasets.has_voted || false,
this.is_favorite = datasets.is_favorite || false,
@@ -944,7 +945,6 @@ openerp.mail = function (session) {
start: function () {
this._super.apply(this, arguments);
- this.expender();
this.bind_events();
if(this.thread_level < this.options.display_indented_thread) {
this.create_thread();
@@ -967,6 +967,8 @@ openerp.mail = function (session) {
this.$('.oe_reply').on('click', this.on_message_reply);
this.$('.oe_star').on('click', this.on_star);
this.$('.oe_msg_vote').on('click', this.on_vote);
+ this.$('.oe_mail_expand').on('click', this.on_expand);
+ this.$('.oe_mail_reduce').on('click', this.on_expand);
this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
},
@@ -995,15 +997,11 @@ openerp.mail = function (session) {
return false;
},
- expender: function () {
- this.$('.oe_msg_body:first').expander({
- slicePoint: this.options.truncate_limit,
- expandText: _t('read more'),
- userCollapseText: _t('read less'),
- detailClass: 'oe_msg_tail',
- moreClass: 'oe_mail_expand',
- lessClass: 'oe_mail_reduce',
- });
+ on_expand: function (event) {
+ event.stopPropagation();
+ this.$('.oe_msg_body_short:first').toggle();
+ this.$('.oe_msg_body_long:first').toggle();
+ return false;
},
/**
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.xml b/addons/mail/static/src/xml/mail.xml
index 94ee1ff452f..1be9754e1c7 100644
--- a/addons/mail/static/src/xml/mail.xml
+++ b/addons/mail/static/src/xml/mail.xml
@@ -266,7 +266,13 @@
-
+
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/i18n/zh_CN.po b/addons/product/i18n/zh_CN.po
index bd28e914c5e..470e1c0632b 100644
--- a/addons/product/i18n/zh_CN.po
+++ b/addons/product/i18n/zh_CN.po
@@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:06+0000\n"
-"PO-Revision-Date: 2012-11-27 09:28+0000\n"
-"Last-Translator: 盈通 ccdos
\n"
+"PO-Revision-Date: 2013-07-28 11:18+0000\n"
+"Last-Translator: Wei \"oldrev\" Li \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-03-16 05:25+0000\n"
-"X-Generator: Launchpad (build 16532)\n"
+"X-Launchpad-Export-Date: 2013-07-29 05:25+0000\n"
+"X-Generator: Launchpad (build 16700)\n"
#. module: product
#: field:product.packaging,rows:0
@@ -513,7 +513,7 @@ msgstr "标准耳机"
#. module: product
#: model:product.uom,name:product.product_uom_day
msgid "Day(s)"
-msgstr ""
+msgstr "天"
#. module: product
#: help:product.product,incoming_qty:0
@@ -800,7 +800,7 @@ msgstr "结束日期"
#. module: product
#: model:product.uom,name:product.product_uom_litre
msgid "Liter(s)"
-msgstr ""
+msgstr "升"
#. module: product
#: view:product.price_list:0
@@ -843,7 +843,7 @@ msgstr "价格表"
#. module: product
#: model:product.uom,name:product.product_uom_hour
msgid "Hour(s)"
-msgstr ""
+msgstr "小时"
#. module: product
#: selection:product.template,state:0
@@ -1114,7 +1114,7 @@ msgstr "右父项"
#. module: product
#: field:product.product,price:0
msgid "Price"
-msgstr ""
+msgstr "价格"
#. module: product
#: field:product.pricelist.item,price_surcharge:0
@@ -1192,7 +1192,7 @@ msgstr "显示产品列表的顺序号"
#. module: product
#: model:product.uom,name:product.product_uom_dozen
msgid "Dozen(s)"
-msgstr ""
+msgstr "打"
#. module: product
#: field:product.uom,factor:0
@@ -1338,7 +1338,7 @@ msgstr "指定一个产品分类 ,规则只被应用在与这个分类以及
#. module: product
#: view:product.product:0
msgid "Inventory"
-msgstr "盘点"
+msgstr "库存"
#. module: product
#: field:product.product,seller_info_id:0
@@ -1346,7 +1346,7 @@ msgid "Supplier Info"
msgstr "供应商信息"
#. module: product
-#: code:addons/product/product.py:729
+#: code:addons/product/product.py:732
#, python-format
msgid "%s (copy)"
msgstr "%s (副本)"
@@ -1891,7 +1891,7 @@ msgstr "消息"
#. module: product
#: model:product.uom,name:product.product_uom_unit
msgid "Unit(s)"
-msgstr ""
+msgstr "件"
#. module: product
#: code:addons/product/product.py:176
@@ -2341,7 +2341,7 @@ msgstr "序列"
msgid ""
"Average delay in days to produce this product. In the case of multi-level "
"BOM, the manufacturing lead times of the components will be added."
-msgstr ""
+msgstr "生成该产品的平均延时天数。在多级BoM的情况下,部件的制造提前期将被累加"
#. module: product
#: model:product.template,name:product.product_assembly_product_template
@@ -2364,6 +2364,8 @@ msgid ""
" \n"
" "
msgstr ""
+"单击以创建一个新的计量单位\n"
+" "
#. module: product
#: model:product.template,name:product.product_product_11_product_template
@@ -2444,7 +2446,7 @@ msgid "15” LCD Monitor"
msgstr "15” LCD 显示器"
#. module: product
-#: code:addons/product/pricelist.py:376
+#: code:addons/product/pricelist.py:379
#: field:product.pricelist.item,base_pricelist_id:0
#, python-format
msgid "Other Pricelist"
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 @@
-
+
@@ -160,7 +160,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.task
form
tree,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/i18n/zh_CN.po b/addons/project/i18n/zh_CN.po
index 8d5f0a04018..2bb41d21e16 100644
--- a/addons/project/i18n/zh_CN.po
+++ b/addons/project/i18n/zh_CN.po
@@ -7,19 +7,19 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
-"PO-Revision-Date: 2013-07-14 04:15+0000\n"
-"Last-Translator: Alan \n"
+"PO-Revision-Date: 2013-08-13 04:04+0000\n"
+"Last-Translator: Wei \"oldrev\" Li \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-07-15 04:51+0000\n"
-"X-Generator: Launchpad (build 16696)\n"
+"X-Launchpad-Export-Date: 2013-08-13 05:00+0000\n"
+"X-Generator: Launchpad (build 16723)\n"
#. module: project
#: view:project.project:0
msgid "Email Interface"
-msgstr "Email 接口"
+msgstr "电子邮件接口"
#. module: project
#: help:account.analytic.account,use_tasks:0
@@ -69,7 +69,7 @@ msgstr "基于该模板新建项目"
#: view:report.project.task.user:0
#: field:report.project.task.user,day:0
msgid "Day"
-msgstr "天"
+msgstr "日"
#. module: project
#: model:project.task.type,name:project.project_tt_merge
@@ -749,7 +749,7 @@ msgstr "待处理任务草稿"
#. module: project
#: field:project.project,alias_model:0
msgid "Alias Model"
-msgstr ""
+msgstr "别名模型"
#. module: project
#: help:report.project.task.user,closing_days:0
@@ -854,7 +854,7 @@ msgstr "增加筛选条件"
#. module: project
#: model:ir.ui.menu,name:project.menu_tasks_config
msgid "GTD"
-msgstr ""
+msgstr "GTD"
#. module: project
#: help:project.task,state:0
@@ -1214,7 +1214,7 @@ msgstr "%s (副本)"
#. module: project
#: model:mail.message.subtype,name:project.mt_project_task_stage
msgid "Task Stage Changed"
-msgstr ""
+msgstr "任务阶段已改变"
#. module: project
#: view:project.task:0
@@ -1706,6 +1706,12 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 单击创建新任务。\n"
+"
\n"
+"OpenERP 系统的任务管理允许你为了更高效地完成工作而管理任务管道。你可以跟踪任务进度、与他人讨论任务、附加文档附件等。\n"
+"
\n"
+" "
#. module: project
#: field:project.task,date_end:0
@@ -2013,7 +2019,7 @@ msgstr "准备好下一个阶段"
#. module: project
#: field:project.task.type,case_default:0
msgid "Default for New Projects"
-msgstr ""
+msgstr "默认用于新项目"
#. module: project
#: view:project.task:0
@@ -2051,6 +2057,8 @@ msgid ""
"resource allocation.\n"
" This installs the module project_long_term."
msgstr ""
+"用于长期项目管理的模块,能够跟踪计划、排程和资源控制。\n"
+" 将会安装模块“project_long_term”。"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_closed
@@ -2083,6 +2091,8 @@ msgid ""
"Provides timesheet support for the issues/bugs management in project.\n"
" This installs the module project_issue_sheet."
msgstr ""
+"为项目中的问题/错误管理提供时间表支持。\n"
+" 将安装模块“project_issue_sheet”。"
#. module: project
#: selection:project.project,privacy_visibility:0
diff --git a/addons/project/project.py b/addons/project/project.py
index 6bd17f98e92..105fcd27d76 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,34 +538,34 @@ 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',
},
}
def _get_default_partner(self, cr, uid, context=None):
- """ Override of base_stage to add project specific behavior """
project_id = self._get_default_project_id(cr, uid, context)
if project_id:
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 +574,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 +651,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 +671,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 +734,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 +746,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 +763,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 +786,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 +914,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 +931,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 +955,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 +970,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 +1010,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 +1036,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 +1075,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 +1106,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 +1134,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 +1258,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 +1292,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 +1328,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 @@
1
Analysis
- draft
-
+
- 2
+ 10
Specification
- pending
- 2
+ 11
Design
- open
- 3
+ 12
Development
- open
- 4
+ 13
Testing
- open
- 5
+ 14
Merge
- open
- 100
+ 20
Done
- done
- 200
+ 30
Cancelled
- cancelled
@@ -86,11 +78,11 @@
Task created
-
- Task Started
+
+ Task Assigned
project.task
- Task started
+ Task Assigned
Task Blocked
@@ -98,12 +90,6 @@
Task blocked
-
- Task Done
- project.task
-
- Task closed
-
Stage Changed
project.task
@@ -118,11 +104,11 @@
project_id
-
- Task Started
+
+ Task Assigned
project.project
-
+
project_id
@@ -131,12 +117,6 @@
project_id
-
- Task Done
- project.project
-
- project_id
-
Task Stage Changed
project.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 @@
X
+
@@ -371,18 +369,6 @@
@@ -396,8 +382,7 @@
-
-
+
+ groups="project.group_time_work_estimation_tasks"/>
-
+
@@ -427,7 +412,7 @@
-
+
@@ -436,7 +421,7 @@
+ groups="project.group_delegate_task"/>
@@ -445,7 +430,6 @@
-
@@ -453,12 +437,11 @@
-
+
-
@@ -467,6 +450,7 @@
+
@@ -493,7 +477,6 @@
-
@@ -513,7 +496,6 @@
2
5
10
- !
@@ -562,7 +544,7 @@
project.task
-
+
@@ -575,7 +557,6 @@
-
@@ -661,7 +642,7 @@
project.task
form
tree,form,calendar,graph,kanban
- [('date_deadline','<',time.strftime('%Y-%m-%d')),('state','in',('draft','pending','open'))]
+ [('date_deadline','<',time.strftime('%Y-%m-%d'))]
@@ -706,7 +687,6 @@
-
@@ -723,7 +703,7 @@
-
+
@@ -767,7 +747,9 @@
-
+
diff --git a/addons/project/report/project_cumulative.xml b/addons/project/report/project_cumulative.xml
index 17e059c8de7..9b9490f0b28 100644
--- a/addons/project/report/project_cumulative.xml
+++ b/addons/project/report/project_cumulative.xml
@@ -14,7 +14,6 @@
-
@@ -40,8 +39,7 @@
-
-
+
diff --git a/addons/project/report/project_report.py b/addons/project/report/project_report.py
index 006aae1085c..15b26507ff3 100644
--- a/addons/project/report/project_report.py
+++ b/addons/project/report/project_report.py
@@ -19,9 +19,10 @@
#
##############################################################################
-from openerp.osv import fields,osv
+from openerp.osv import fields, osv
from openerp import tools
+
class report_project_task_user(osv.osv):
_name = "report.project.task.user"
_description = "Tasks by user and project"
@@ -31,10 +32,11 @@ class report_project_task_user(osv.osv):
'day': fields.char('Day', size=128, readonly=True),
'year': fields.char('Year', size=64, required=False, readonly=True),
'user_id': fields.many2one('res.users', 'Assigned To', readonly=True),
- 'date_start': fields.date('Starting Date',readonly=True),
+ 'date_start': fields.date('Assignation Date', readonly=True),
'no_of_days': fields.integer('# of Days', size=128, readonly=True),
'date_end': fields.date('Ending Date', readonly=True),
'date_deadline': fields.date('Deadline', readonly=True),
+ 'date_last_stage_update': fields.date('Last Stage Update', readonly=True),
'project_id': fields.many2one('project.project', 'Project', readonly=True),
'hours_planned': fields.float('Planned Hours', readonly=True),
'hours_effective': fields.float('Effective Hours', readonly=True),
@@ -44,16 +46,17 @@ class report_project_task_user(osv.osv):
'total_hours': fields.float('Total Hours', readonly=True),
'closing_days': fields.float('Days to Close', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to close the task"),
- 'opening_days': fields.float('Days to Open', digits=(16,2), readonly=True, group_operator="avg",
+ 'opening_days': fields.float('Days to Assign', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to Open the task"),
'delay_endings_days': fields.float('Overpassed Deadline', digits=(16,2), readonly=True),
'nbr': fields.integer('# of tasks', readonly=True),
- 'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'),
-('0','Very urgent')], 'Priority', readonly=True),
- 'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month', readonly=True),
+ 'priority': fields.selection([('4', 'Very Low'), ('3', 'Low'), ('2', 'Medium'), ('1', 'Urgent'), ('0', 'Very urgent')],
+ string='Priority', readonly=True),
+ 'month':fields.selection(fields.date.MONTHS, 'Month', readonly=True),
'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')],'Status', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'partner_id': fields.many2one('res.partner', 'Contact', readonly=True),
+ 'stage_id': fields.many2one('project.task.type', 'Stage'),
}
_order = 'name desc, project_id'
@@ -69,19 +72,19 @@ class report_project_task_user(osv.osv):
to_char(date_start, 'YYYY-MM-DD') as day,
date_trunc('day',t.date_start) as date_start,
date_trunc('day',t.date_end) as date_end,
+ date_trunc('day',t.date_last_stage_update) as date_last_stage_update,
to_date(to_char(t.date_deadline, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_deadline,
-- sum(cast(to_char(date_trunc('day',t.date_end) - date_trunc('day',t.date_start),'DD') as int)) as no_of_days,
abs((extract('epoch' from (t.date_end-t.date_start)))/(3600*24)) as no_of_days,
t.user_id,
progress as progress,
t.project_id,
- t.state,
t.effective_hours as hours_effective,
t.priority,
t.name as name,
t.company_id,
t.partner_id,
- t.stage_id,
+ t.stage_id as stage_id,
remaining_hours as remaining_hours,
total_hours as total_hours,
t.delay_hours as hours_delay,
@@ -106,15 +109,12 @@ class report_project_task_user(osv.osv):
date_start,
date_end,
date_deadline,
+ date_last_stage_update,
t.user_id,
t.project_id,
- t.state,
t.priority,
name,
t.company_id,
t.partner_id,
- t.stage_id
-
+ stage_id
""")
-
-
diff --git a/addons/project/report/project_report_view.xml b/addons/project/report/project_report_view.xml
index d9650e165ce..d5638e332a4 100644
--- a/addons/project/report/project_report_view.xml
+++ b/addons/project/report/project_report_view.xml
@@ -13,11 +13,12 @@
+
-
+
@@ -44,7 +45,7 @@
-
+
@@ -58,31 +59,29 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/addons/project/security/ir.model.access.csv b/addons/project/security/ir.model.access.csv
index a76faa0fcce..c88fb276fcb 100644
--- a/addons/project/security/ir.model.access.csv
+++ b/addons/project/security/ir.model.access.csv
@@ -19,6 +19,7 @@ access_project_task_history,project.task.history project,project.model_project_t
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0
access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0
+access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_project_category,project.project_category,model_project_category,,1,0,0,0
access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
diff --git a/addons/project/test/project_demo.yml b/addons/project/test/project_demo.yml
deleted file mode 100644
index 5f6acbb913e..00000000000
--- a/addons/project/test/project_demo.yml
+++ /dev/null
@@ -1,12 +0,0 @@
--
- !record {model: project.project, id: project_project_1, view: False}:
- partner_id: base.res_partner_2
--
- !record {model: project.task, id: project_task_1, view: False}:
- remaining_hours: 10.00
--
- !record {model: project.task, id: project_task_1, view: False}:
- planned_hours: 10.00
--
- !record {model: project.task, id: project_task_1, view: False}:
- project_id: project_project_1
\ No newline at end of file
diff --git a/addons/project/test/project_process.yml b/addons/project/test/project_process.yml
deleted file mode 100644
index c2a5c072e9d..00000000000
--- a/addons/project/test/project_process.yml
+++ /dev/null
@@ -1,70 +0,0 @@
--
- In order to Test Process of Project Management,
--
- I create duplicate template.
--
- !python {model: project.project}: |
- new_template = self.duplicate_template(cr, uid, [ref("project_project_1")])
- assert new_template, "duplicate template is not created"
- template = self.browse(cr, uid, new_template['res_id'], context=context)
- assert template.state == 'open', "Duplicate template must be in open state."
--
- I convert template into real Project.
--
- !python {model: project.project}: |
- self.reset_project(cr, uid, [ref("project_project_1")])
--
- I check project details after convert from template.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be active}:
- - state == "open"
--
- I put project in pending.
--
- !python {model: project.project}: |
- self.set_pending(cr, uid, [ref("project_project_1")])
--
- I check state after put in pending.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be in pending state}:
- - state == "pending"
--
- I re-open the project.
--
- !python {model: project.project}: |
- self.set_open(cr, uid, [ref("project_project_1")])
--
- I check state after reopen.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be open.}:
- - state == "open"
--
- I close the project.
--
- !python {model: project.project}: |
- self.set_done(cr, uid, [ref("project_project_1")])
--
- I check state after closed.
--
- !assert {model: project.project, id: project_project_1, severity: error, string: Project should be close.}:
- - state == "close"
--
- I set project into template.
--
- !python {model: project.project}: |
- self.set_template(cr, uid, [ref("project_project_1")])
--
- I schedule tasks of project.
--
- !python {model: project.project}: |
- self.schedule_tasks(cr, uid, [ref("project_project_1")], context=context)
--
- I copy the tasks of project.
--
- !python {model: project.project}: |
- self.copy(cr, uid, ref("project_project_1"))
--
- I cancel Project.
--
- !python {model: project.project}: |
- self.set_cancel(cr, uid, [ref("project_project_2")])
diff --git a/addons/project/test/task_process.yml b/addons/project/test/task_process.yml
deleted file mode 100644
index e37b2116714..00000000000
--- a/addons/project/test/task_process.yml
+++ /dev/null
@@ -1,76 +0,0 @@
--
- I put task in pending due to specification is not clear.
--
- !python {model: project.task}: |
- self.do_pending(cr, uid, [ref("project_task_1")])
- context.update({"active_id": ref("project_task_1")})
--
- I check state of task after put in pending.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be in pending state}:
- - state == "pending"
--
- !record {model: project.task.delegate, id: delegate_id}:
- user_id: base.user_demo
- planned_hours: 12.0
- planned_hours_me: 2.0
--
- Now I delegate task to team member.
--
- !python {model: project.task.delegate}: |
- self.delegate(cr, uid, [ref("delegate_id")], {"active_id": ref("project_task_1")})
--
- I check delegated task details.
--
- !python {model: project.task}: |
- task = self.browse(cr, uid, ref("project_task_1"), context=context)
- assert task.planned_hours == 2.0, "Planning hours is not correct after delegated."
- assert task.state == "pending", "Task should be in Pending after delegated."
--
- I re-open the task.
--
- !python {model: project.task}: |
- self.do_reopen(cr, uid, [ref("project_task_1")])
--
- I check reopened task details.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be open.}:
- - state == "open"
--
- I change the stage of task to next stage.
--
- !python {model: project.task}: |
- self.stage_next(cr, uid, [ref("project_task_1")])
--
- !record {model: project.task.reevaluate, id: reevaluate_id}:
- remaining_hours : 120
--
- I reevaluate task with remaining hours.
--
- !python {model: project.task.reevaluate}: |
- self.compute_hours(cr, uid, [ref("reevaluate_id")], {"active_id": ref("project_task_1")})
--
- I check remaining hours after reevaluated task.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task should be reevaluated}:
- - remaining_hours == 120.0
--
- I close the task.
--
- !python {model: project.task}: |
- self.action_close(cr, uid, [ref("project_task_1")])
--
- I check state after closed.
--
- !assert {model: project.task, id: project_task_1, severity: error, string: task is in open state}:
- - state == "done"
--
- I change the stage of task to previous stage.
--
- !python {model: project.task}: |
- self.stage_previous(cr, uid, [ref("project_task_1")])
--
- I cancel Task.
--
- !python {model: project.task}: |
- self.do_cancel(cr, uid, [ref("project_task_2")])
diff --git a/addons/project/tests/__init__.py b/addons/project/tests/__init__.py
new file mode 100644
index 00000000000..4d3163d6dc6
--- /dev/null
+++ b/addons/project/tests/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from . import test_project_flow
+
+checks = [
+ test_project_flow,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/tests/test_project_base.py b/addons/project/tests/test_project_base.py
new file mode 100644
index 00000000000..f82561766fe
--- /dev/null
+++ b/addons/project/tests/test_project_base.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.mail.tests.test_mail_base import TestMailBase
+
+
+class TestProjectBase(TestMailBase):
+
+ def setUp(self):
+ super(TestProjectBase, self).setUp()
+ cr, uid = self.cr, self.uid
+
+ # Usefull models
+ self.project_project = self.registry('project.project')
+ self.project_task = self.registry('project.task')
+ self.project_task_delegate = self.registry('project.task.delegate')
+
+ # Find Project User group
+ group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
+ self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
+
+ # Find Project Manager group
+ group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
+ self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
+
+ # Test partners to use through the various tests
+ self.project_partner_id = self.res_partner.create(cr, uid, {
+ 'name': 'Gertrude AgrolaitPartner',
+ 'email': 'gertrude.partner@agrolait.com',
+ })
+ self.email_partner_id = self.res_partner.create(cr, uid, {
+ 'name': 'Patrick Ratatouille',
+ 'email': 'patrick.ratatouille@agrolait.com',
+ })
+
+ # Test users to use through the various tests
+ self.user_projectuser_id = self.res_users.create(cr, uid, {
+ 'name': 'Armande ProjectUser',
+ 'login': 'Armande',
+ 'alias_name': 'armande',
+ 'email': 'armande.projectuser@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
+ })
+ self.user_projectmanager_id = self.res_users.create(cr, uid, {
+ 'name': 'Bastien ProjectManager',
+ 'login': 'bastien',
+ 'alias_name': 'bastien',
+ 'email': 'bastien.projectmanager@example.com',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_manager_id])]
+ })
+ self.user_none_id = self.res_users.create(cr, uid, {
+ 'name': 'Charlie Avotbonkeur',
+ 'login': 'charlie',
+ 'alias_name': 'charlie',
+ 'email': 'charlie.noone@example.com',
+ 'groups_id': [(6, 0, [])]
+ })
+ self.user_projectuser = self.res_users.browse(cr, uid, self.user_projectuser_id)
+ self.user_projectmanager = self.res_users.browse(cr, uid, self.user_projectmanager_id)
+ self.partner_projectuser_id = self.user_projectuser.partner_id.id
+ self.partner_projectmanager_id = self.user_projectmanager.partner_id.id
+
+ # Test 'Pigs' project
+ self.project_pigs_id = self.project_project.create(cr, uid, {
+ 'name': 'Pigs',
+ 'privacy_visibility': 'public',
+ 'alias_name': 'project+pigs',
+ 'partner_id': self.partner_raoul_id,
+ }, {'mail_create_nolog': True})
+
+ # Already-existing tasks in Pigs
+ self.task_1_id = self.project_task.create(cr, uid, {
+ 'name': 'Pigs UserTask',
+ 'user_id': self.user_projectuser_id,
+ 'project_id': self.project_pigs_id,
+ }, {'mail_create_nolog': True})
+ self.task_2_id = self.project_task.create(cr, uid, {
+ 'name': 'Pigs ManagerTask',
+ 'user_id': self.user_projectmanager_id,
+ 'project_id': self.project_pigs_id,
+ }, {'mail_create_nolog': True})
diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py
new file mode 100644
index 00000000000..a037c92d2ae
--- /dev/null
+++ b/addons/project/tests/test_project_flow.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2013-TODAY OpenERP S.A.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+
+from openerp.addons.project.tests.test_project_base import TestProjectBase
+from openerp.osv.orm import except_orm
+from openerp.tools import mute_logger
+
+
+EMAIL_TPL = """Return-Path:
+X-Original-To: {email_to}
+Delivered-To: {email_to}
+To: {email_to}
+Received: by mail1.openerp.com (Postfix, from userid 10002)
+ id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
+Message-ID: {msg_id}
+Date: Tue, 29 Nov 2011 12:43:21 +0530
+From: {email_from}
+MIME-Version: 1.0
+Subject: {subject}
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+
+Hello,
+
+This email should create a new entry in your module. Please check that it
+effectively works.
+
+Thanks,
+
+--
+Raoul Boitempoils
+Integrator at Agrolait"""
+
+
+class TestProjectFlow(TestProjectBase):
+
+ @mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
+ def test_00_project_process(self):
+ """ Testing project management """
+ cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
+
+ # ProjectUser: set project as template -> raise
+ self.assertRaises(except_orm, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id])
+
+ # Other tests are done using a ProjectManager
+ project = self.project_project.browse(cr, user_projectmanager_id, project_pigs_id)
+ self.assertNotEqual(project.state, 'template', 'project: incorrect state, should not be a template')
+
+ # Set test project as template
+ self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'template', 'project: set_template: project state should be template')
+ self.assertEqual(len(project.tasks), 0, 'project: set_template: project tasks should have been set inactive')
+
+ # Duplicate template
+ new_template_act = self.project_project.duplicate_template(cr, user_projectmanager_id, [project_pigs_id])
+ new_project = self.project_project.browse(cr, user_projectmanager_id, new_template_act['res_id'])
+ self.assertEqual(new_project.state, 'open', 'project: incorrect duplicate_template')
+ self.assertEqual(len(new_project.tasks), 2, 'project: duplicating a project template should duplicate its tasks')
+
+ # Convert into real project
+ self.project_project.reset_project(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'open', 'project: resetted project should be in open state')
+ self.assertEqual(len(project.tasks), 2, 'project: reset_project: project tasks should have been set active')
+
+ # Put as pending
+ self.project_project.set_pending(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'pending', 'project: should be in pending state')
+
+ # Re-open
+ self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'open', 'project: reopened project should be in open state')
+
+ # Close project
+ self.project_project.set_done(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+ self.assertEqual(project.state, 'close', 'project: closed project should be in close state')
+
+ # Re-open
+ self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
+ project.refresh()
+
+ # Re-convert into a template and schedule tasks
+ self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
+ self.project_project.schedule_tasks(cr, user_projectmanager_id, [project_pigs_id])
+
+ # Copy the project
+ new_project_id = self.project_project.copy(cr, user_projectmanager_id, project_pigs_id)
+ new_project = self.project_project.browse(cr, user_projectmanager_id, new_project_id)
+ self.assertEqual(len(new_project.tasks), 2, 'project: copied project should have copied task')
+
+ # Cancel the project
+ self.project_project.set_cancel(cr, user_projectmanager_id, [project_pigs_id])
+ self.assertEqual(project.state, 'cancelled', 'project: cancelled project should be in cancel state')
+
+ def test_10_task_process(self):
+ """ Testing task creation and management """
+ cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
+
+ def format_and_process(template, email_to='project+pigs@mydomain.com, other@gmail.com', subject='Frogs',
+ email_from='Patrick Ratatouille ',
+ msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
+ self.assertEqual(self.project_task.search(cr, uid, [('name', '=', subject)]), [])
+ mail = template.format(email_to=email_to, subject=subject, email_from=email_from, msg_id=msg_id)
+ self.mail_thread.message_process(cr, uid, None, mail)
+ return self.project_task.search(cr, uid, [('name', '=', subject)])
+
+ # Do: incoming mail from an unknown partner on an alias creates a new task 'Frogs'
+ frogs = format_and_process(EMAIL_TPL)
+
+ # Test: one task created by mailgateway administrator
+ self.assertEqual(len(frogs), 1, 'project: message_process: a new project.task should have been created')
+ task = self.project_task.browse(cr, user_projectuser_id, frogs[0])
+ res = self.project_task.perm_read(cr, uid, [task.id], details=False)
+ self.assertEqual(res[0].get('create_uid'), uid,
+ 'project: message_process: task should have been created by uid as alias_user_id is False on the alias')
+ # Test: messages
+ self.assertEqual(len(task.message_ids), 3,
+ 'project: message_process: newly created task should have 2 messages: creation and email')
+ self.assertEqual(task.message_ids[2].subtype_id.name, 'Task Created',
+ 'project: message_process: first message of new task should have Task Created subtype')
+ self.assertEqual(task.message_ids[1].subtype_id.name, 'Task Assigned',
+ 'project: message_process: first message of new task should have Task Created subtype')
+ self.assertEqual(task.message_ids[0].author_id.id, self.email_partner_id,
+ 'project: message_process: second message should be the one from Agrolait (partner failed)')
+ self.assertEqual(task.message_ids[0].subject, 'Frogs',
+ 'project: message_process: second message should be the one from Agrolait (subject failed)')
+ # Test: task content
+ self.assertEqual(task.name, 'Frogs', 'project_task: name should be the email subject')
+ self.assertEqual(task.project_id.id, self.project_pigs_id, 'project_task: incorrect project')
+ self.assertEqual(task.stage_id.sequence, 1, 'project_task: should have a stage with sequence=1')
+
+ # Open the delegation wizard
+ delegate_id = self.project_task_delegate.create(cr, user_projectuser_id, {
+ 'user_id': user_projectuser_id,
+ 'planned_hours': 12.0,
+ 'planned_hours_me': 2.0,
+ }, {'active_id': task.id})
+ self.project_task_delegate.delegate(cr, user_projectuser_id, [delegate_id], {'active_id': task.id})
+
+ # Check delegation details
+ task.refresh()
+ self.assertEqual(task.planned_hours, 2, 'project_task_delegate: planned hours is not correct after delegation')
diff --git a/addons/project/wizard/__init__.py b/addons/project/wizard/__init__.py
index 7a9a734b2bd..24017d9cbe8 100644
--- a/addons/project/wizard/__init__.py
+++ b/addons/project/wizard/__init__.py
@@ -20,7 +20,5 @@
##############################################################################
import project_task_delegate
-import project_task_reevaluate
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/project/wizard/project_task_reevaluate.py b/addons/project/wizard/project_task_reevaluate.py
deleted file mode 100644
index eeb8cccc5d2..00000000000
--- a/addons/project/wizard/project_task_reevaluate.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL ().
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
-
-from lxml import etree
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-
-class project_task_reevaluate(osv.osv_memory):
- _name = 'project.task.reevaluate'
-
- def _get_remaining(self, cr, uid, context=None):
- if context is None:
- context = {}
- active_id = context.get('active_id', False)
- res = False
- if active_id:
- res = self.pool.get('project.task').browse(cr, uid, active_id, context=context).remaining_hours
- return res
-
- _columns = {
- 'remaining_hours' : fields.float('Remaining Hours', digits=(16,2), help="Put here the remaining hours required to close the task."),
- }
-
- _defaults = {
- 'remaining_hours': _get_remaining,
- }
-
- def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
- res = super(project_task_reevaluate, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu=submenu)
- users_pool = self.pool.get('res.users')
- time_mode = users_pool.browse(cr, uid, uid, context).company_id.project_time_mode_id
- time_mode_name = time_mode and time_mode.name or 'Hours'
- if time_mode_name in ['Hours','Hour']:
- return res
-
- eview = etree.fromstring(res['arch'])
-
- def _check_rec(eview):
- if eview.attrib.get('widget','') == 'float_time':
- eview.set('widget','float')
- for child in eview:
- _check_rec(child)
- return True
-
- _check_rec(eview)
-
- res['arch'] = etree.tostring(eview)
-
- for field in res['fields']:
- if 'Hours' in res['fields'][field]['string']:
- res['fields'][field]['string'] = res['fields'][field]['string'].replace('Hours',time_mode_name)
- return res
-
- def compute_hours(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
- data = self.browse(cr, uid, ids, context=context)[0]
- task_id = context.get('active_id')
- if task_id:
- task_pool = self.pool.get('project.task')
- task_pool.write(cr, uid, task_id, {'remaining_hours': data.remaining_hours})
- if context.get('button_reactivate'):
- task_pool.do_reopen(cr, uid, [task_id], context=context)
- return {'type': 'ir.actions.act_window_close'}
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/project/wizard/project_task_reevaluate_view.xml b/addons/project/wizard/project_task_reevaluate_view.xml
deleted file mode 100644
index 6dfe58e8cfd..00000000000
--- a/addons/project/wizard/project_task_reevaluate_view.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
- Re-evaluate Task
- project.task.reevaluate
-
-
-
-
-
-
-
-
-
-
-
-
- Re-evaluate Task
- ir.actions.act_window
- project.task.reevaluate
- form
- form
- new
-
-
-
diff --git a/addons/project_gtd/project_gtd_view.xml b/addons/project_gtd/project_gtd_view.xml
index 4bfd5b783c7..8335ae35c93 100644
--- a/addons/project_gtd/project_gtd_view.xml
+++ b/addons/project_gtd/project_gtd_view.xml
@@ -79,7 +79,6 @@
-
@@ -103,16 +102,17 @@
-
-
+
-
-
-
+
+
+
+
+
diff --git a/addons/project_gtd/wizard/project_gtd_empty.py b/addons/project_gtd/wizard/project_gtd_empty.py
index 3233e2534af..b477c12567a 100644
--- a/addons/project_gtd/wizard/project_gtd_empty.py
+++ b/addons/project_gtd/wizard/project_gtd_empty.py
@@ -52,7 +52,7 @@ class project_timebox_empty(osv.osv_memory):
raise osv.except_osv(_('Error!'), _('No timebox child of this one!'))
tids = obj_task.search(cr, uid, [('timebox_id', '=', context['active_id'])])
for task in obj_task.browse(cr, uid, tids, context):
- if (task.state in ('cancel','done')) or (task.user_id.id <> uid):
+ if (task.stage_id and task.stage_id.fold) or (task.user_id.id <> uid):
close.append(task.id)
else:
up.append(task.id)
diff --git a/addons/project_issue/__openerp__.py b/addons/project_issue/__openerp__.py
index 2c5f311c044..d41144253dd 100644
--- a/addons/project_issue/__openerp__.py
+++ b/addons/project_issue/__openerp__.py
@@ -55,7 +55,6 @@ It allows the manager to quickly check the issues, assign them and decide on the
'test': [
'test/subscribe_issue.yml',
'test/issue_process.yml',
- 'test/cancel_issue.yml',
'test/issue_demo.yml'
],
'installable': True,
diff --git a/addons/project_issue/board_project_issue_view.xml b/addons/project_issue/board_project_issue_view.xml
index d7574042c37..6eb48f5572d 100644
--- a/addons/project_issue/board_project_issue_view.xml
+++ b/addons/project_issue/board_project_issue_view.xml
@@ -6,7 +6,7 @@
Project Issue Board Tree
project.issue
-
+
@@ -16,7 +16,6 @@
-
@@ -28,7 +27,7 @@
project.issue
form
tree,form
- [('state','not in',('cancel','done')),('user_id','=',uid)]
+ [('user_id', '=', uid)]
diff --git a/addons/project_issue/doc/changelog.rst b/addons/project_issue/doc/changelog.rst
new file mode 100644
index 00000000000..e74516f1600
--- /dev/null
+++ b/addons/project_issue/doc/changelog.rst
@@ -0,0 +1,13 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+- Stage/state update
+
+ - ``project.issue``: removed inheritance from ``base_stage`` class and removed
+ ``state`` field. Added ``date_last_stage_update`` field holding last stage_id
+ modification. Updated reports.
diff --git a/addons/project_issue/doc/index.rst b/addons/project_issue/doc/index.rst
new file mode 100644
index 00000000000..9e12eef2124
--- /dev/null
+++ b/addons/project_issue/doc/index.rst
@@ -0,0 +1,22 @@
+=====================
+Project Issue DevDoc
+=====================
+
+Project Issue module documentation
+===================================
+
+Documentation topics
+''''''''''''''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ stage_status.rst
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/project_issue/doc/stage_status.rst b/addons/project_issue/doc/stage_status.rst
new file mode 100644
index 00000000000..6afd4508cd9
--- /dev/null
+++ b/addons/project_issue/doc/stage_status.rst
@@ -0,0 +1,54 @@
+.. _stage_status:
+
+Stage and Status
+================
+
+.. versionchanged:: 8.0 saas-2 state/stage cleaning
+
+Stage
++++++
+
+This revision removed the concept of state on project.issue objects. The ``state``
+field has been totally removed and replaced by stages, using ``stage_id``. The
+following models are impacted:
+
+ - ``project.issue`` now use only stages. However a convention still exists about
+ 'New' stage. An issue 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.issue.report`` do not have any ``state`` field anymore.
+
+By default a newly created issue 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.issue.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.issue``:
+
+ - ``mt_issue_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
+ - ``mt_issue_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
+ - ``mt_issue_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
+ - ``mt_issue_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_issue/i18n/bs.po b/addons/project_issue/i18n/bs.po
new file mode 100644
index 00000000000..db49b0c586c
--- /dev/null
+++ b/addons/project_issue/i18n/bs.po
@@ -0,0 +1,984 @@
+# Bosnian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:04+0000\n"
+"PO-Revision-Date: 2013-08-06 17:46+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Bosnian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
+"X-Generator: Launchpad (build 16721)\n"
+
+#. module: project_issue
+#: model:project.category,name:project_issue.project_issue_category_03
+msgid "Deadly bug"
+msgstr "Ubojita greška"
+
+#. module: project_issue
+#: help:project.config.settings,fetchmail_issue:0
+msgid ""
+"Allows you to configure your incoming mail server, and create issues from "
+"incoming emails."
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,delay_open:0
+msgid "Avg. Delay to Open"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: view:project.issue.report:0
+msgid "Group By..."
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,working_hours_open:0
+msgid "Working Hours to Open the Issue"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,description:project_issue.mt_issue_started
+msgid "Issue started"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date_open:0
+msgid "Opened"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,opening_date:0
+msgid "Date of Opening"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "March"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,progress:0
+msgid "Progress (%)"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,message_unread:0
+msgid "Unread Messages"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,company_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,company_id:0
+msgid "Company"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,email_cc:0
+msgid "Watchers Emails"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,kanban_state:0
+msgid ""
+"A Issue's kanban state indicates special situations affecting it:\n"
+" * Normal is the default situation\n"
+" * Blocked indicates something is preventing the progress of this issue\n"
+" * Ready for next stage indicates the issue is ready to be pulled to the "
+"next stage"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,message_unread:0
+msgid "If checked new messages require your attention."
+msgstr ""
+
+#. module: project_issue
+#: help:account.analytic.account,use_issues:0
+msgid "Check this field if this project manages issues"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,day_open:0
+msgid "Days to Open"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:479
+#, python-format
+msgid ""
+"You cannot escalate this issue.\n"
+"The relevant Project has not configured the Escalation Project!"
+msgstr ""
+
+#. module: project_issue
+#: constraint:project.project:0
+msgid "Error! You cannot assign escalation to the same project!"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,priority:0
+#: selection:project.issue.report,priority:0
+msgid "Highest"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,inactivity_days:0
+msgid "Difference in days between last action and current date"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+#: field:project.issue.report,day:0
+msgid "Day"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,days_since_creation:0
+msgid "Days since creation date"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,task_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,task_id:0
+msgid "Task"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_project_issue_stage
+msgid "Issue Stage Changed"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,message_ids:0
+msgid "Messages"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,inactivity_days:0
+msgid "Days since last action"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_project_project
+#: view:project.issue:0
+#: field:project.issue,project_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,project_id:0
+msgid "Project"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,help:project_issue.project_issue_categ_act0
+msgid ""
+"\n"
+" Click to report a new issue.\n"
+"
\n"
+" The OpenERP issues tacker allows you to efficiantly manage "
+"things\n"
+" like internal requests, software development bugs, customer\n"
+" complaints, project troubles, material breakdowns, etc.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,state:0
+#: selection:project.issue.report,state:0
+msgid "Cancelled"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,description:0
+msgid "Private Note"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,date_closed:0
+msgid "Date of Closing"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Issue Tracker Search"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,color:0
+msgid "Color Index"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,working_hours_open:0
+msgid "Avg. Working Hours to Open"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_account_analytic_account
+msgid "Analytic Account"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,message_summary:0
+msgid ""
+"Holds the Chatter summary (number of messages, ...). This summary is "
+"directly in html format in order to be inserted in kanban views."
+msgstr ""
+
+#. module: project_issue
+#: help:project.project,project_escalation_id:0
+msgid ""
+"If any issue is escalated from the current Project, it will be listed under "
+"the project selected here."
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Extra Info"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,help:project_issue.action_project_issue_report
+msgid ""
+"This report on the project issues allows you to analyse the quality of your "
+"support or after-sales services. You can track the issues per age. You can "
+"analyse the time required to open or close an issue, the number of email to "
+"exchange and the time spent on average by issues."
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Edit..."
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Responsible"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Statistics"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,kanban_state:0
+msgid "Kanban State"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:366
+#, python-format
+msgid "Project issue converted to task."
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,priority:0
+#: view:project.issue.report:0
+#: field:project.issue.report,priority:0
+msgid "Priority"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,version_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,version_id:0
+msgid "Version"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,message_follower_ids:0
+msgid "Followers"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: selection:project.issue,state:0
+#: view:project.issue.report:0
+msgid "New"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,name:project_issue.project_issue_categ_action
+msgid "Issue Categories"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,email_from:0
+msgid "Email"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,channel_id:0
+#: field:project.issue.report,channel_id:0
+msgid "Channel"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,priority:0
+#: selection:project.issue.report,priority:0
+msgid "Lowest"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:388
+#, python-format
+msgid "%s (copy)"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Unassigned Issues"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,create_date:0
+#: view:project.issue.report:0
+#: field:project.issue.report,creation_date:0
+msgid "Creation Date"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,name:project_issue.project_issue_version_action
+#: model:ir.ui.menu,name:project_issue.menu_project_issue_version_act
+msgid "Versions"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "To Do Issues"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_project_issue_version
+msgid "project.issue.version"
+msgstr ""
+
+#. module: project_issue
+#: field:project.config.settings,fetchmail_issue:0
+msgid "Create issues from an incoming email account "
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: selection:project.issue,state:0
+#: view:project.issue.report:0
+msgid "Done"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "July"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.ui.menu,name:project_issue.menu_project_issue_category_act
+msgid "Categories"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,stage_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,stage_id:0
+msgid "Stage"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,name:project_issue.action_project_issue_report
+#: model:ir.ui.menu,name:project_issue.menu_project_issue_report_tree
+#: view:project.issue.report:0
+msgid "Issues Analysis"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:516
+#, python-format
+msgid "No Subject"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,name:project_issue.action_view_my_project_issue_tree
+msgid "My Project Issues"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,partner_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,partner_id:0
+msgid "Contact"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Delete"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:371
+#, python-format
+msgid "Tasks"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,nbr:0
+msgid "# of Issues"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "September"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "December"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,categ_ids:0
+msgid "Tags"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Issue Tracker Tree"
+msgstr ""
+
+#. module: project_issue
+#: model:project.category,name:project_issue.project_issue_category_01
+msgid "Little problem"
+msgstr ""
+
+#. module: project_issue
+#: view:project.project:0
+msgid "creates"
+msgstr ""
+
+#. module: project_issue
+#: model:crm.case.categ,name:project_issue.feature_request_categ
+msgid "Feature Requests"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,write_date:0
+msgid "Update Date"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Project:"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Open Features"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date_action_next:0
+msgid "Next Action"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: selection:project.issue,kanban_state:0
+msgid "Blocked"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,user_email:0
+msgid "User Email"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+msgid "#Number of Project Issues"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,channel_id:0
+msgid "Communication channel."
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,email_cc:0
+msgid ""
+"These email addresses will be added to the CC field of all inbound and "
+"outbound emails for this record before being sent. Separate multiple email "
+"addresses with a comma"
+msgstr ""
+
+#. module: project_issue
+#: model:crm.case.categ,name:project_issue.bug_categ
+msgid "Maintenance"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,state:0
+msgid "Draft"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,priority:0
+#: selection:project.issue.report,priority:0
+msgid "Low"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date_closed:0
+#: selection:project.issue.report,state:0
+msgid "Closed"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,delay_close:0
+msgid "Avg. Delay to Close"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,state:0
+#: view:project.issue.report:0
+#: selection:project.issue.report,state:0
+msgid "Pending"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,state:0
+#: field:project.issue.report,state:0
+msgid "Status"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+msgid "#Project Issues"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "August"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,kanban_state:0
+#: selection:project.issue,priority:0
+#: selection:project.issue.report,priority:0
+msgid "Normal"
+msgstr ""
+
+#. module: project_issue
+#: field:project.project,issue_count:0
+msgid "unknown"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Category:"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "June"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,message_ids:0
+msgid "Messages and communication history"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "New Issues"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,day_close:0
+msgid "Days to Close"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,message_is_follower:0
+msgid "Is a Follower"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,state:0
+msgid ""
+"The status is set to 'Draft', when a case is created. "
+"If the case is in progress the status is set to 'Open'. "
+"When the case is over, the status is set to 'Done'. If "
+"the case needs to be reviewed then the status is set "
+"to 'Pending'."
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,active:0
+#: field:project.issue.version,active:0
+msgid "Active"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "November"
+msgstr ""
+
+#. module: project_issue
+#: code:addons/project_issue/project_issue.py:479
+#, python-format
+msgid "Warning!"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+msgid "Search"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "October"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,days_since_creation:0
+msgid "Difference in days between creation date and current date"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "January"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Feature Tracker Tree"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,email_from:0
+msgid "These people will receive email."
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,message_summary:0
+msgid "Summary"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date:0
+msgid "Date"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,user_id:0
+#: view:project.issue.report:0
+#: field:project.issue.report,user_id:0
+msgid "Assigned to"
+msgstr ""
+
+#. module: project_issue
+#: view:project.config.settings:0
+msgid "Configure"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,description:project_issue.mt_issue_closed
+msgid "Issue closed"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Current Features"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.version:0
+msgid "Issue Version"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.version,name:0
+msgid "Version Number"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Cancel"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,state:0
+msgid "Open"
+msgstr ""
+
+#. module: project_issue
+#: field:account.analytic.account,use_issues:0
+#: model:ir.actions.act_window,name:project_issue.act_project_project_2_project_issue_all
+#: model:ir.actions.act_window,name:project_issue.project_issue_categ_act0
+#: model:ir.ui.menu,name:project_issue.menu_project_confi
+#: model:ir.ui.menu,name:project_issue.menu_project_issue_track
+#: view:project.issue:0
+#: view:project.project:0
+msgid "Issues"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: selection:project.issue,state:0
+msgid "In Progress"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: view:project.issue.report:0
+msgid "To Do"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_project_issue
+#: view:project.issue.report:0
+msgid "Project Issue"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Creation Month"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,progress:0
+msgid "Computed as: Time Spent / Total Time."
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: selection:project.issue,kanban_state:0
+msgid "Ready for next stage"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+#: field:project.issue.report,section_id:0
+msgid "Sale Team"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: view:project.issue.report:0
+#: field:project.issue.report,month:0
+msgid "Month"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+#: field:project.issue,name:0
+#: view:project.project:0
+msgid "Issue"
+msgstr ""
+
+#. module: project_issue
+#: model:project.category,name:project_issue.project_issue_category_02
+msgid "PBCK"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Feature Tracker Search"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Description"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,section_id:0
+msgid "Sales Team"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "May"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_project_config_settings
+msgid "project.config.settings"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_issue_closed
+#: model:mail.message.subtype,name:project_issue.mt_project_issue_closed
+msgid "Issue Closed"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+#: field:project.issue.report,email:0
+msgid "# Emails"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_issue_new
+#: model:mail.message.subtype,name:project_issue.mt_project_issue_new
+msgid "Issue Created"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_issue_blocked
+#: model:mail.message.subtype,name:project_issue.mt_project_issue_blocked
+msgid "Issue Blocked"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "February"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,description:project_issue.mt_issue_stage
+#: model:mail.message.subtype,description:project_issue.mt_project_issue_stage
+msgid "Stage changed"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "Feature description"
+msgstr ""
+
+#. module: project_issue
+#: field:project.project,project_escalation_id:0
+msgid "Project Escalation"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.actions.act_window,help:project_issue.project_issue_version_action
+msgid ""
+"\n"
+" Click to add a new version.\n"
+"
\n"
+" Define here the different versions of your products on "
+"which\n"
+" you can work on issues.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue,section_id:0
+msgid ""
+"Sales team to which Case belongs to. Define "
+"Responsible user and Email account for mail gateway."
+msgstr ""
+
+#. module: project_issue
+#: view:board.board:0
+msgid "My Issues"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue.report,delay_open:0
+msgid "Number of Days to open the project issue."
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue.report,month:0
+msgid "April"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "⇒ Escalate"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue:0
+msgid "References"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,description:project_issue.mt_issue_new
+msgid "Issue created"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,working_hours_close:0
+msgid "Working Hours to Close the Issue"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,id:0
+msgid "ID"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,description:project_issue.mt_issue_blocked
+msgid "Issue blocked"
+msgstr ""
+
+#. module: project_issue
+#: model:ir.model,name:project_issue.model_project_issue_report
+msgid "project.issue.report"
+msgstr ""
+
+#. module: project_issue
+#: help:project.issue.report,delay_close:0
+msgid "Number of Days to close the project issue"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue.report,working_hours_close:0
+msgid "Avg. Working Hours to Close"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_issue_stage
+msgid "Stage Changed"
+msgstr ""
+
+#. module: project_issue
+#: selection:project.issue,priority:0
+#: selection:project.issue.report,priority:0
+msgid "High"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date_deadline:0
+msgid "Deadline"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,date_action_last:0
+msgid "Last Action"
+msgstr ""
+
+#. module: project_issue
+#: view:project.issue.report:0
+#: field:project.issue.report,name:0
+msgid "Year"
+msgstr ""
+
+#. module: project_issue
+#: field:project.issue,duration:0
+msgid "Duration"
+msgstr ""
+
+#. module: project_issue
+#: model:mail.message.subtype,name:project_issue.mt_issue_started
+#: model:mail.message.subtype,name:project_issue.mt_project_issue_started
+msgid "Issue Started"
+msgstr ""
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 91b5633f6d6..81104985c1b 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -19,19 +19,16 @@
#
##############################################################################
-from openerp import SUPERUSER_ID
-from openerp.addons.base_status.base_stage import base_stage
-from openerp.addons.project.project import _TASK_STATE
-from openerp.addons.crm import crm
from datetime import datetime
-from openerp.osv import fields, osv, orm
-from openerp.tools.translate import _
-import binascii
-import time
-from openerp import tools
-from openerp.tools import html2plaintext
-class project_issue_version(osv.osv):
+from openerp import SUPERUSER_ID
+from openerp import tools
+from openerp.addons.crm import crm
+from openerp.osv import fields, osv, orm
+from openerp.tools import html2plaintext
+from openerp.tools.translate import _
+
+class project_issue_version(osv.Model):
_name = "project.issue.version"
_order = "name desc"
_columns = {
@@ -42,44 +39,33 @@ class project_issue_version(osv.osv):
'active': 1,
}
-class project_issue(base_stage, osv.osv):
+class project_issue(osv.Model):
_name = "project.issue"
_description = "Project Issue"
_order = "priority, create_date desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
+ _mail_post_access = 'read'
_track = {
- 'state': {
- 'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
- 'project_issue.mt_issue_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
- 'project_issue.mt_issue_started': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
- },
'stage_id': {
- 'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'done', 'open'],
+ 'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
+ 'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1,
+ },
+ 'user_id': {
+ 'project_issue.mt_issue_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
},
'kanban_state': {
'project_issue.mt_issue_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
},
}
- def create(self, cr, uid, vals, context=None):
- if context is None:
- context = {}
- if vals.get('project_id') and not context.get('default_project_id'):
- context['default_project_id'] = vals.get('project_id')
-
- # context: no_log, because subtype already handle this
- create_context = dict(context, mail_create_nolog=True)
- return super(project_issue, self).create(cr, uid, vals, context=create_context)
-
def _get_default_partner(self, cr, uid, context=None):
- """ Override of base_stage to add project specific behavior """
project_id = self._get_default_project_id(cr, uid, context)
if project_id:
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(project_issue, self)._get_default_partner(cr, uid, context=context)
+ return False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default project by checking if present in the context """
@@ -88,7 +74,7 @@ class project_issue(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'
@@ -258,13 +244,6 @@ class project_issue(base_stage, osv.osv):
'partner_id': fields.many2one('res.partner', 'Contact', select=1),
'company_id': fields.many2one('res.company', 'Company'),
'description': fields.text('Private Note'),
- '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\'.'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
help="A Issue's kanban state indicates special situations affecting it:\n"
@@ -278,6 +257,7 @@ class project_issue(base_stage, osv.osv):
# Project Issue fields
'date_closed': fields.datetime('Closed', readonly=True,select=True),
'date': fields.datetime('Date'),
+ 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."),
'categ_ids': fields.many2many('project.category', string='Tags'),
'priority': fields.selection(crm.AVAILABLE_PRIORITIES, 'Priority', select=True),
@@ -313,13 +293,11 @@ class project_issue(base_stage, osv.osv):
_defaults = {
'active': 1,
- 'partner_id': lambda s, cr, uid, c: s._get_default_partner(cr, uid, c),
- 'email_from': lambda s, cr, uid, c: s._get_default_email(cr, uid, c),
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
- 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
'priority': crm.AVAILABLE_PRIORITIES[2][0],
'kanban_state': 'normal',
+ 'date_last_stage_update': fields.datetime.now(),
'user_id': lambda obj, cr, uid, context: uid,
}
@@ -373,7 +351,7 @@ class project_issue(base_stage, osv.osv):
})
vals = {
'task_id': new_task_id,
- 'stage_id': self.stage_find(cr, uid, [bug], bug.project_id.id, [('state', '=', 'pending')], context=context),
+ 'stage_id': self.stage_find(cr, uid, [bug], bug.project_id.id, [('sequence', '=', 1)], context=context),
}
message = _("Project issue converted to task.")
self.message_post(cr, uid, [bug.id], body=message, context=context)
@@ -401,19 +379,23 @@ class project_issue(base_stage, osv.osv):
return super(project_issue, self).copy(cr, uid, id, default=default,
context=context)
+ def create(self, cr, uid, vals, context=None):
+ if context is None:
+ context = {}
+ if vals.get('project_id') and not context.get('default_project_id'):
+ context['default_project_id'] = vals.get('project_id')
+
+ # context: no_log, because subtype already handle this
+ create_context = dict(context, mail_create_nolog=True)
+ return super(project_issue, self).create(cr, uid, vals, context=create_context)
+
def write(self, cr, uid, ids, vals, context=None):
-
- #Update last action date every time the user changes the stage
+ # stage change: update date_last_stage_update
if 'stage_id' in vals:
- vals['date_action_last'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
- state = self.pool.get('project.task.type').browse(cr, uid, vals['stage_id'], context=context).state
- for issue in self.browse(cr, uid, ids, context=context):
- # Change from draft to not draft EXCEPT cancelled: The issue has been opened -> set the opening date
- if issue.state == 'draft' and state not in ('draft', 'cancelled'):
- vals['date_open'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
- # Change from not done to done: The issue has been closed -> set the closing date
- if issue.state != 'done' and state == 'done':
- vals['date_closed'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
+ 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()
return super(project_issue, self).write(cr, uid, ids, vals, context)
@@ -423,13 +405,6 @@ class project_issue(base_stage, osv.osv):
task = self.pool.get('project.task').browse(cr, uid, task_id, context=context)
return {'value': {'user_id': task.user_id.id, }}
- def case_reset(self, cr, uid, ids, context=None):
- """Resets case as draft
- """
- res = super(project_issue, self).case_reset(cr, uid, ids, context)
- self.write(cr, uid, ids, {'date_open': False, 'date_closed': False})
- return res
-
def get_empty_list_help(self, cr, uid, help, context=None):
context['empty_list_help_model'] = 'project.project'
context['empty_list_help_id'] = context.get('default_project_id')
@@ -478,11 +453,6 @@ class project_issue(base_stage, osv.osv):
return stage_ids[0]
return False
- def case_cancel(self, cr, uid, ids, context=None):
- """ Cancels case """
- self.case_set(cr, uid, ids, 'cancelled', {'active': True}, context=context)
- return True
-
def case_escalate(self, cr, uid, ids, context=None):
cases = self.browse(cr, uid, ids)
for case in cases:
@@ -507,17 +477,6 @@ class project_issue(base_stage, osv.osv):
return [issue.project_id.message_get_reply_to()[0] if issue.project_id else False
for issue 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(project_issue, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
-
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(project_issue, self).message_get_suggested_recipients(cr, uid, ids, context=context)
try:
@@ -591,7 +550,7 @@ class project_issue(base_stage, osv.osv):
context = {}
res = super(project_issue, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
if thread_id and subtype:
- self.write(cr, SUPERUSER_ID, thread_id, {'date_action_last': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
+ self.write(cr, SUPERUSER_ID, thread_id, {'date_action_last': fields.datetime.now()}, context=context)
return res
@@ -605,7 +564,7 @@ class project(osv.Model):
res = dict.fromkeys(ids, 0)
issue_ids = self.pool.get('project.issue').search(cr, uid, [('project_id', 'in', ids)])
for issue in self.pool.get('project.issue').browse(cr, uid, issue_ids, context):
- if issue.state not in ('done', 'cancelled'):
+ if issue.stage_id and not issue.stage_id.fold:
res[issue.project_id.id] += 1
return res
diff --git a/addons/project_issue/project_issue_data.xml b/addons/project_issue/project_issue_data.xml
index 2e43ca69193..25a336d4094 100644
--- a/addons/project_issue/project_issue_data.xml
+++ b/addons/project_issue/project_issue_data.xml
@@ -35,11 +35,11 @@ Access all issues from the top Project menu, and access the issues of a specific
Issue created
-
- Issue Started
+
+ Issue Assigned
project.issue
- Issue started
+ Issue assigned
Issue Blocked
@@ -47,12 +47,6 @@ Access all issues from the top Project menu, and access the issues of a specific
Issue blocked
-
- Issue Closed
- project.issue
-
- Issue closed
-
Stage Changed
project.issue
@@ -67,11 +61,11 @@ Access all issues from the top Project menu, and access the issues of a specific
project_id
-
- Issue Started
+
+ Issue Assigned
project.project
-
+
project_id
@@ -80,12 +74,6 @@ Access all issues from the top Project menu, and access the issues of a specific
project_id
-
- Issue Closed
- project.project
-
- project_id
-
Issue Stage Changed
project.project
diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml
index da3034c72f5..1fbea33f3df 100644
--- a/addons/project_issue/project_issue_view.xml
+++ b/addons/project_issue/project_issue_view.xml
@@ -48,12 +48,6 @@
@@ -76,7 +70,7 @@
-
@@ -106,7 +100,6 @@
-
@@ -123,7 +116,7 @@
Project Issue Tracker Tree
project.issue
-
+
@@ -135,7 +128,6 @@
-
@@ -149,17 +141,16 @@
-
+
+
+
-
-
-
-
+
-
+
@@ -269,7 +260,7 @@
Project Issue- Feature Tracker Tree
project.issue
-
+
@@ -278,7 +269,6 @@
-
diff --git a/addons/project_issue/report/project_issue_report.py b/addons/project_issue/report/project_issue_report.py
index 5cca0ff8689..5f59b51f290 100644
--- a/addons/project_issue/report/project_issue_report.py
+++ b/addons/project_issue/report/project_issue_report.py
@@ -20,17 +20,10 @@
#
##############################################################################
-from openerp.osv import fields,osv
+from openerp.osv import fields, osv
from openerp import tools
from openerp.addons.crm import crm
-AVAILABLE_STATES = [
- ('draft','Draft'),
- ('open','Open'),
- ('cancel', 'Cancelled'),
- ('done', 'Closed'),
- ('pending','Pending')
-]
class project_issue_report(osv.osv):
_name = "project.issue.report"
_auto = False
@@ -38,18 +31,13 @@ class project_issue_report(osv.osv):
_columns = {
'name': fields.char('Year', size=64, required=False, readonly=True),
'section_id':fields.many2one('crm.case.section', 'Sale Team', readonly=True),
- 'state': fields.selection(AVAILABLE_STATES, 'Status', size=16, readonly=True),
- 'month':fields.selection([('01', 'January'), ('02', 'February'), \
- ('03', 'March'), ('04', 'April'),\
- ('05', 'May'), ('06', 'June'), \
- ('07', 'July'), ('08', 'August'),\
- ('09', 'September'), ('10', 'October'),\
- ('11', 'November'), ('12', 'December')], 'Month', readonly=True),
+ 'month':fields.selection(fields.date.MONTHS, 'Month', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'day': fields.char('Day', size=128, readonly=True),
'opening_date': fields.date('Date of Opening', readonly=True),
'creation_date': fields.date('Creation Date', readonly=True),
'date_closed': fields.date('Date of Closing', readonly=True),
+ 'date_last_stage_update': fields.date('Last Stage Update', readonly=True),
'stage_id': fields.many2one('project.task.type', 'Stage'),
'nbr': fields.integer('# of Issues', readonly=True),
'working_hours_open': fields.float('Avg. Working Hours to Open', readonly=True, group_operator="avg"),
@@ -80,7 +68,7 @@ class project_issue_report(osv.osv):
to_char(c.create_date, 'YYYY-MM-DD') as day,
to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
to_char(c.create_date, 'YYYY-MM-DD') as creation_date,
- c.state,
+ date_trunc('day',c.date_last_stage_update) as date_last_stage_update,
c.user_id,
c.working_hours_open,
c.working_hours_close,
diff --git a/addons/project_issue/report/project_issue_report_view.xml b/addons/project_issue/report/project_issue_report_view.xml
index a885dcd8a7f..f9ca7168841 100644
--- a/addons/project_issue/report/project_issue_report_view.xml
+++ b/addons/project_issue/report/project_issue_report_view.xml
@@ -20,7 +20,7 @@
-
+
@@ -36,9 +36,9 @@
project.issue.report
-
-
+
+
@@ -49,14 +49,15 @@
-
-
-
-
+
+
+
+
@@ -70,6 +71,7 @@
+
diff --git a/addons/project_issue/test/cancel_issue.yml b/addons/project_issue/test/cancel_issue.yml
deleted file mode 100644
index 76ffa212638..00000000000
--- a/addons/project_issue/test/cancel_issue.yml
+++ /dev/null
@@ -1,60 +0,0 @@
--
- In order to test process of issue tracking in OpenERP, I cancel the unqualified Issue.
--
- !python {model: project.issue}: |
- self.case_cancel(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the issue is in cancel state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}:
- - state == 'cancelled'
--
- I re-open the Issue.
--
- !python {model: project.issue}: |
- self.case_open(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the state of issue after open it.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in open state}:
- - state == 'open'
--
- I put the issue in pending state.
--
- !python {model: project.issue}: |
- self.case_pending(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the state of issue after put it in pending state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in pending state}:
- - state == 'pending'
--
- I cancel the issue is in pending state.
--
- !python {model: project.issue}: |
- self.case_cancel(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the issue is in cancel state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}:
- - state == 'cancelled'
--
- I close Issue.
--
- !python {model: project.issue}: |
- self.case_close(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state of Issue after close.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in done state}:
- - state == 'done'
--
- I cancel the issue is in done state.
--
- !python {model: project.issue}: |
- self.case_cancel(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check the issue is in cancel state.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue is in cancel state}:
- - state == 'cancelled'
diff --git a/addons/project_issue/test/issue_process.yml b/addons/project_issue/test/issue_process.yml
index afb68a9b6c2..280df50ebea 100644
--- a/addons/project_issue/test/issue_process.yml
+++ b/addons/project_issue/test/issue_process.yml
@@ -1,23 +1,3 @@
--
- In order to test process of issue tracking in OpenERP, I Open the Issue.
--
- !python {model: project.issue}: |
- self.case_open(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state of Issue after opened it.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in open state}:
- - state == 'open'
--
- Now I put Issue in pending due to need more information.
--
- !python {model: project.issue}: |
- self.case_pending(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state after put in pending.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in pending state}:
- - state == 'pending'
-
I send mail to get more details. TODO revert mail.mail to mail.compose.message (conversion to customer should be automatic).
-
@@ -29,29 +9,9 @@
new_id = self.create(cr, uid, {'email_from': 'support@mycompany.com','email_to': 'Robert_Adersen@yahoo.com', 'subject': 'Regarding error in account module we nees more details'})
self.send_mail(cr, uid, [new_id], context=ctx)
except Exception, e:
- pass
--
- After getting sufficient details, I re-open Issue from pending state.
--
- !python {model: project.issue}: |
- self.case_open(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I check state of Issue after re-opened.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in open state}:
- - state == 'open'
+ pass
-
I create Task for Issue.
-
!python {model: project.issue}: |
self.convert_issue_task(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I close Issue after resolving it
--
- !python {model: project.issue}: |
- self.case_close(cr, uid, [ref("crm_case_buginaccountsmodule0")])
--
- I Check state of Issue after closed.
--
- !assert {model: project.issue, id: crm_case_buginaccountsmodule0, severity: error, string: Issue should be in done state}:
- - state == 'done'
diff --git a/addons/project_long_term/project_long_term_view.xml b/addons/project_long_term/project_long_term_view.xml
index e61af0c6c32..e2f47489b4a 100644
--- a/addons/project_long_term/project_long_term_view.xml
+++ b/addons/project_long_term/project_long_term_view.xml
@@ -200,7 +200,7 @@
-
+
diff --git a/addons/project_long_term/security/ir.model.access.csv b/addons/project_long_term/security/ir.model.access.csv
index 2a00088b8f4..82bfab460cb 100644
--- a/addons/project_long_term/security/ir.model.access.csv
+++ b/addons/project_long_term/security/ir.model.access.csv
@@ -4,9 +4,6 @@ access_project_user_allocation,project.user.allocation,model_project_user_alloca
access_project_phase_manager,project.phase manager,model_project_phase,project.group_project_manager,1,1,1,1
access_project_user_allocation_manager,project.user.allocation manager,model_project_user_allocation,project.group_project_manager,1,1,1,1
access_resource_resource_user,user.user user,resource.model_resource_resource,project.group_project_user,1,0,0,0
-access_resource_calendar_leaves_user,user.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_resource_resource_manager,user.user manager,resource.model_resource_resource,project.group_project_manager,1,1,1,1
access_project_user_allocation_manager,project.user.allocation.manager,model_project_user_allocation,project.group_project_manager,1,1,1,1
access_project_resource_calendar_attendance,resource.calendar.attendance,resource.model_resource_calendar_attendance,project.group_project_manager,1,0,0,0
-
-
diff --git a/addons/project_long_term/test/task_process.yml b/addons/project_long_term/test/task_process.yml
index a7065de315f..060abcf43f3 100644
--- a/addons/project_long_term/test/task_process.yml
+++ b/addons/project_long_term/test/task_process.yml
@@ -14,6 +14,6 @@
!python {model: project.project}: |
prj = self.browse(cr, uid, [ref("project.project_project_1")])[0]
for task in prj.tasks:
- if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
assert task.user_id and task.date_start and task.date_end, "Project tasks not scheduled"
diff --git a/addons/project_mrp/project_mrp.py b/addons/project_mrp/project_mrp.py
index bb25b4aae24..d5ed55a8d31 100644
--- a/addons/project_mrp/project_mrp.py
+++ b/addons/project_mrp/project_mrp.py
@@ -22,6 +22,23 @@
from openerp.osv import fields, osv
from openerp import netsvc
+
+class ProjectTaskStageMrp(osv.Model):
+ """ Override project.task.type model to add a 'closed' boolean field allowing
+ to know that tasks in this stage are considered as closed. Indeed since
+ OpenERP 8.0 status is not present on tasks anymore, only stage_id. """
+ _name = 'project.task.type'
+ _inherit = 'project.task.type'
+
+ _columns = {
+ 'closed': fields.boolean('Close', help="Tasks in this stage are considered as closed."),
+ }
+
+ _defaults = {
+ 'closed': False,
+ }
+
+
class project_task(osv.osv):
_name = "project.task"
_inherit = "project.task"
@@ -30,21 +47,21 @@ class project_task(osv.osv):
'sale_line_id': fields.related('procurement_id', 'sale_line_id', type='many2one', relation='sale.order.line', store=True, string='Sales Order Line'),
}
- def _validate_subflows(self, cr, uid, ids):
+ def _validate_subflows(self, cr, uid, ids, context=None):
wf_service = netsvc.LocalService("workflow")
for task in self.browse(cr, uid, ids):
if task.procurement_id:
wf_service.trg_write(uid, 'procurement.order', task.procurement_id.id, cr)
- def do_close(self, cr, uid, ids, *args, **kwargs):
- res = super(project_task, self).do_close(cr, uid, ids, *args, **kwargs)
- self._validate_subflows(cr, uid, ids)
+ def write(self, cr, uid, ids, values, context=None):
+ """ When closing tasks, validate subflows. """
+ res = super(project_task, self).write(cr, uid, ids, values, context=context)
+ if values.get('stage_id'):
+ stage = self.pool.get('project.task.type').browse(cr, uid, values.get('stage_id'), context=context)
+ if stage.closed:
+ self._validate_subflows(cr, uid, ids, context=context)
return res
- def do_cancel(self, cr, uid, ids, *args, **kwargs):
- res = super(project_task, self).do_cancel(cr, uid, ids, *args, **kwargs)
- self._validate_subflows(cr, uid, ids)
- return res
class product_product(osv.osv):
_inherit = "product.product"
@@ -52,8 +69,9 @@ class product_product(osv.osv):
'project_id': fields.many2one('project.project', 'Project', ondelete='set null',)
}
+
class sale_order(osv.osv):
- _inherit ='sale.order'
+ _inherit = 'sale.order'
def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
proc_data = super(sale_order, self)._prepare_order_line_procurement(cr,
@@ -66,11 +84,12 @@ class sale_order(osv.osv):
return {}
res_sale = {}
res = super(sale_order, self)._picked_rate(cr, uid, ids, name, arg, context=context)
- cr.execute('''select sol.order_id as sale_id, t.state as task_state ,
+ cr.execute('''select sol.order_id as sale_id, stage.closed as task_closed ,
t.id as task_id, sum(sol.product_uom_qty) as total
from project_task as t
left join sale_order_line as sol on sol.id = t.sale_line_id
- where sol.order_id in %s group by sol.order_id,t.state,t.id ''',(tuple(ids),))
+ left join project_task_type as stage on stage.id = t.stage_id
+ where sol.order_id in %s group by sol.order_id,stage.closed,t.id ''',(tuple(ids),))
sale_task_data = cr.dictfetchall()
if not sale_task_data:
@@ -90,7 +109,7 @@ class sale_order(osv.osv):
for item in sale_task_data:
res_sale[item['sale_id']]['total_no_task'] += item['total']
- if item['task_state'] == 'done':
+ if item['task_closed']:
res_sale[item['sale_id']]['number_of_done'] += item['total']
for sale in self.browse(cr, uid, ids, context=context):
diff --git a/addons/project_mrp/project_mrp_demo.xml b/addons/project_mrp/project_mrp_demo.xml
index 30d4cfba7ef..a840c0166e7 100644
--- a/addons/project_mrp/project_mrp_demo.xml
+++ b/addons/project_mrp/project_mrp_demo.xml
@@ -1,7 +1,6 @@
-
@@ -14,5 +13,8 @@
make_to_order
+
+
+
diff --git a/addons/project_mrp/project_mrp_view.xml b/addons/project_mrp/project_mrp_view.xml
index 499a0259214..f724a3c76b6 100644
--- a/addons/project_mrp/project_mrp_view.xml
+++ b/addons/project_mrp/project_mrp_view.xml
@@ -21,14 +21,24 @@
+
+ project.task.type.mrp.inherit
+ project.task.type
+
+
+
+
+
+
+
project.mrp.form.view.inherit
project.task
-
+
-
+
diff --git a/addons/project_mrp/project_procurement.py b/addons/project_mrp/project_procurement.py
index f4b4a28db46..8a6dc811d9a 100644
--- a/addons/project_mrp/project_procurement.py
+++ b/addons/project_mrp/project_procurement.py
@@ -33,13 +33,13 @@ class procurement_order(osv.osv):
def action_check_finished(self, cr, uid, ids):
res = super(procurement_order, self).action_check_finished(cr, uid, ids)
return res and self.check_task_done(cr, uid, ids)
-
+
def check_task_done(self, cr, uid, ids, context=None):
""" Checks if task is done or not.
@return: True or False.
"""
for p in self.browse(cr, uid, ids, context=context):
- if (p.product_id.type=='service') and (p.procure_method=='make_to_order') and p.task_id and (p.task_id.state not in ('done', 'cancelled')):
+ if (p.product_id.type == 'service') and (p.procure_method == 'make_to_order') and p.task_id and (p.task_id.stage_id and not p.task_id.stage_id.closed):
return False
return True
diff --git a/addons/project_mrp/test/project_task_procurement.yml b/addons/project_mrp/test/project_task_procurement.yml
index 967461ca30a..b16cce7590d 100644
--- a/addons/project_mrp/test/project_task_procurement.yml
+++ b/addons/project_mrp/test/project_task_procurement.yml
@@ -36,7 +36,7 @@
!python {model: project.task}: |
task_ids = self.search(cr, uid, [('sale_line_id', '=', ref('line_services'))])
assert task_ids, "Task is not generated for Service Order Line."
- self.do_close(cr, uid, task_ids, context=context)
+ self.write(cr, uid, task_ids, {'stage_id': ref('project.project_tt_deployment')}, context=context)
-
I check procurement of Service Order Line after closed task.
-
@@ -44,4 +44,4 @@
procurement_ids = self.search(cr, uid, [('sale_line_id', '=', ref('line_services'))])
assert procurement_ids, "Procurement is not generated for Service Order Line."
procurement = self.browse(cr, uid, procurement_ids[0], context=context)
- assert procurement.state == 'done' , "Procurement should be closed."
+ assert procurement.state == 'done' , "Procurement should be closed."
\ No newline at end of file
diff --git a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
index 3efd133ffb6..7d7566bdd0c 100644
--- a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
+++ b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml
@@ -66,16 +66,7 @@
planned_hours: 20.0
project_id: project_project_timesheetmanagement0
remaining_hours: 20.0
- state: draft
user_id: res_users_hrmanager0
--
- Open the task
--
- !python {model: project.task}: |
- self.do_open(cr, uid, [ref("project_task_getalltimesheetrecords0")], {"lang":
- "en_US", "active_ids": [ref("project_project_timesheetmanagement0")], "tz":
- False, "active_model": "project.project", "department_id": False, "project_id":
- False, "active_id": ref("project_project_timesheetmanagement0"), })
-
Make a work task entry 'Get work calendar of all employees' of 10 hours done by HR manager
-
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 91fb2525be6..dffe1ed54ff 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -174,8 +174,8 @@
-
-
+
+
@@ -292,7 +292,7 @@
-
+
diff --git a/addons/purchase/report/request_quotation.rml b/addons/purchase/report/request_quotation.rml
index b7b4776d635..cfc97a75aa4 100644
--- a/addons/purchase/report/request_quotation.rml
+++ b/addons/purchase/report/request_quotation.rml
@@ -95,7 +95,7 @@
-
+
Description
@@ -110,7 +110,7 @@
[[ repeatIn(order.order_line,'order_line') ]]
-
+
[[ order_line.name ]]
@@ -119,10 +119,10 @@
[[ formatLang(order_line.date_planned, date = True) ]]
-
- [[ formatLang(order_line.product_qty )]]
- [[ (order_line.product_uom and order_line.product_uom.name) or '' ]]
-
+ [[ formatLang(order_line.product_qty )]]
+
+
+ [[ (order_line.product_uom and order_line.product_uom.name) or '' ]]
diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml
index ae21934ce61..c02f19ced65 100644
--- a/addons/sale_crm/sale_crm_view.xml
+++ b/addons/sale_crm/sale_crm_view.xml
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/addons/sale_crm/wizard/crm_make_sale.py b/addons/sale_crm/wizard/crm_make_sale.py
index b74817059e4..829468e684f 100644
--- a/addons/sale_crm/wizard/crm_make_sale.py
+++ b/addons/sale_crm/wizard/crm_make_sale.py
@@ -112,7 +112,7 @@ class crm_make_sale(osv.osv_memory):
message = _("Opportunity has been converted to the quotation %s .") % (sale_order.name)
case.message_post(body=message)
if make.close:
- case_obj.case_close(cr, uid, data)
+ case_obj.case_mark_won(cr, uid, data, context=context)
if not new_ids:
return {'type': 'ir.actions.act_window_close'}
if len(new_ids)<=1:
diff --git a/addons/share/i18n/bs.po b/addons/share/i18n/bs.po
new file mode 100644
index 00000000000..7d3a0e10b80
--- /dev/null
+++ b/addons/share/i18n/bs.po
@@ -0,0 +1,629 @@
+# Bosnian translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:06+0000\n"
+"PO-Revision-Date: 2013-08-08 22:26+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Bosnian \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-08-09 05:06+0000\n"
+"X-Generator: Launchpad (build 16723)\n"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:842
+#, python-format
+msgid "Invitation to collaborate about %s"
+msgstr "Poziv za razgovor o %s"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:780
+#, python-format
+msgid ""
+"The share engine has not been able to fetch a record_id for your invitation."
+msgstr ""
+"Sistem za dijeljenje nije bio u mogućnosti da uhvati record_id za vašu "
+"pozivnicu."
+
+#. module: share
+#: view:share.wizard:0
+msgid "Include an Optional Personal Message"
+msgstr "Uključi opcionalnu personalnu poruku"
+
+#. module: share
+#: field:share.wizard,embed_option_title:0
+msgid "Display title"
+msgstr "Prikaži naslov"
+
+#. module: share
+#: view:share.wizard:0
+msgid "Access granted!"
+msgstr "Pristup odobren!"
+
+#. module: share
+#: field:share.wizard,record_name:0
+msgid "Record name"
+msgstr "Ime zapisa"
+
+#. module: share
+#: help:share.wizard,message:0
+msgid ""
+"An optional personal message, to be included in the email notification."
+msgstr ""
+"Opcionalna personalna poruka, da bude uključena u email obavještenje."
+
+#. module: share
+#: field:share.wizard,user_type:0
+msgid "Sharing method"
+msgstr "Metoda djeljenja"
+
+#. module: share
+#: field:share.wizard,name:0
+msgid "Share Title"
+msgstr "Djeli naslov"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:848
+#: code:addons/share/wizard/share_wizard.py:877
+#, python-format
+msgid ""
+"The documents are not attached, you can view them online directly on my "
+"OpenERP server at:\n"
+" %s\n"
+"\n"
+msgstr ""
+"Ovi dokumenti nisu zakačeni, možete ih pregledati online direktno na mom "
+"OpenERP serveru na:\n"
+" %s\n"
+"\n"
+
+#. module: share
+#: model:ir.module.category,name:share.module_category_share
+msgid "Sharing"
+msgstr "Dijeljeno"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:843
+#: code:addons/share/wizard/share_wizard.py:875
+#, python-format
+msgid ""
+"Hello,\n"
+"\n"
+msgstr ""
+"Pozdrav,\n"
+"\n"
+
+#. module: share
+#: field:share.wizard,share_root_url:0
+msgid "Share Access URL"
+msgstr "Dijeli URL pristupa"
+
+#. module: share
+#: field:share.wizard,email_1:0
+#: field:share.wizard,email_2:0
+#: field:share.wizard,email_3:0
+msgid "New user email"
+msgstr "Novi korisnički email"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:887
+#, python-format
+msgid "You may use your current login (%s) and password to view them.\n"
+msgstr ""
+"Možete koristiti vašu trenutnu prijavu (%s) i šifru da ih pregledate..\n"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:621
+#, python-format
+msgid "(Modified)"
+msgstr "(promjenjeno)"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:665
+#, python-format
+msgid "You must be a member of the Share/User group to use the share wizard."
+msgstr ""
+"Morate biti član Dijeli/Korisnik grupe da bi ste koristili čarobnjak "
+"dijeljenja."
+
+#. module: share
+#. openerp-web
+#: code:addons/share/static/src/js/share.js:63
+#, python-format
+msgid "Embed"
+msgstr "Ugradi"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:599
+#, python-format
+msgid "Sharing filter created by user %s (%s) for group %s"
+msgstr "Djeljeni filter kreiran od strane korisnika %s (%s) za grupu %s"
+
+#. module: share
+#: field:share.wizard,embed_url:0
+#: field:share.wizard.result.line,share_url:0
+msgid "Share URL"
+msgstr "URL djeljenja"
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:849
+#: code:addons/share/wizard/share_wizard.py:881
+#, python-format
+msgid "These are your credentials to access this protected area:\n"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Access info"
+msgstr ""
+
+#. module: share
+#. openerp-web
+#: code:addons/share/static/src/js/share.js:60
+#: view:share.wizard:0
+#, python-format
+msgid "Share"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:571
+#, python-format
+msgid "(Duplicated for modified sharing permissions)"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:669
+#, python-format
+msgid ""
+"Please indicate the emails of the persons to share with, one per line."
+msgstr ""
+
+#. module: share
+#: help:share.wizard,domain:0
+msgid "Optional domain for further data filtering"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Next"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:662
+#, python-format
+msgid "Action and Access Mode are required to create a shared access."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:850
+#: code:addons/share/wizard/share_wizard.py:882
+#, python-format
+msgid "Username"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Sharing Options"
+msgstr ""
+
+#. module: share
+#. openerp-web
+#: code:addons/share/static/src/xml/share.xml:9
+#, python-format
+msgid "Invite"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Embedded code options"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Configuration"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid ""
+"Please select the action that opens the screen containing the data you want "
+"to share."
+msgstr ""
+
+#. module: share
+#: field:res.groups,share:0
+msgid "Share Group"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:835
+#: code:addons/share/wizard/share_wizard.py:866
+#, python-format
+msgid "Email required"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid ""
+"Optionally, you may specify an additional domain restriction that will be "
+"applied to the shared data."
+msgstr ""
+
+#. module: share
+#: view:res.groups:0
+msgid "Non-Share Groups"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid ""
+"An email notification with instructions has been sent to the following "
+"people:"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:77
+#, python-format
+msgid "Direct link or embed code"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:856
+#: code:addons/share/wizard/share_wizard.py:890
+#, python-format
+msgid ""
+"OpenERP is a powerful and user-friendly suite of Business Applications (CRM, "
+"Sales, HR, etc.)\n"
+"It is open source and can be found on http://www.openerp.com."
+msgstr ""
+
+#. module: share
+#: field:share.wizard,action_id:0
+msgid "Action to share"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,record_name:0
+msgid "Name of the shared record, if sharing a precise record"
+msgstr ""
+
+#. module: share
+#: field:res.users,share:0
+msgid "Share User"
+msgstr ""
+
+#. module: share
+#: field:share.wizard.result.line,user_id:0
+msgid "unknown"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:61
+#: code:addons/share/wizard/share_wizard.py:657
+#, python-format
+msgid "Sharing access cannot be created."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:780
+#, python-format
+msgid "Record id not found"
+msgstr ""
+
+#. module: share
+#: help:res.groups,share:0
+msgid "Group created to set access rights for sharing data with some users."
+msgstr ""
+
+#. module: share
+#: view:res.groups:0
+msgid "Share Groups"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,action_id:0
+msgid ""
+"The action that opens the screen containing the data you wish to share."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:546
+#, python-format
+msgid "(Copy for sharing)"
+msgstr ""
+
+#. module: share
+#: field:share.wizard.result.line,newly_created:0
+msgid "Newly created"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,name:0
+msgid "Title for the share (displayed to users as menu and shortcut name)"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:636
+#, python-format
+msgid "Indirect sharing filter created by user %s (%s) for group %s"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,share_root_url:0
+msgid "Main access page for users that are granted shared access"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:206
+#, python-format
+msgid ""
+"You must configure your email address in the user preferences before using "
+"the Share button."
+msgstr ""
+
+#. module: share
+#: model:res.groups,name:share.group_share_user
+msgid "User"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:658
+#, python-format
+msgid ""
+"Sorry, the current screen and filter you are trying to share are not "
+"supported at the moment.\n"
+"You may want to try a simpler filter."
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Use this link"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:852
+#: code:addons/share/wizard/share_wizard.py:884
+#, python-format
+msgid "Database"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Share with these People (one email per line)"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,domain:0
+msgid "Domain"
+msgstr ""
+
+#. module: share
+#: view:res.groups:0
+msgid "{'search_default_no_share':1}"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+#: field:share.wizard,result_line_ids:0
+msgid "Summary"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,embed_code:0
+msgid ""
+"Embed this code in your documents to provide a link to the shared document."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:513
+#, python-format
+msgid "Copied access for sharing"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:817
+#, python-format
+msgid "Invitation"
+msgstr ""
+
+#. module: share
+#: model:ir.actions.act_window,name:share.action_share_wizard_step1
+msgid "Share your documents"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Or insert the following code where you want to embed your documents"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:886
+#, python-format
+msgid ""
+"The documents have been automatically added to your current OpenERP "
+"documents.\n"
+msgstr ""
+
+#. module: share
+#: model:ir.model,name:share.model_share_wizard_result_line
+msgid "share.wizard.result.line"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,embed_code:0
+msgid "Code"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,user_type:0
+msgid "Select the type of user(s) you would like to share data with."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:844
+#, python-format
+msgid ""
+"I have shared %s (%s) with you!\n"
+"\n"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,view_type:0
+msgid "Current View Type"
+msgstr ""
+
+#. module: share
+#: selection:share.wizard,access_mode:0
+msgid "Can view"
+msgstr ""
+
+#. module: share
+#: selection:share.wizard,access_mode:0
+msgid "Can edit"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Cancel"
+msgstr ""
+
+#. module: share
+#: help:res.users,share:0
+msgid ""
+"External user with limited access, created only for the purpose of sharing "
+"data."
+msgstr ""
+
+#. module: share
+#: model:ir.actions.act_window,name:share.action_share_wizard
+#: model:ir.model,name:share.model_share_wizard
+#: field:share.wizard.result.line,share_wizard_id:0
+msgid "Share Wizard"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:793
+#, python-format
+msgid "Shared access created!"
+msgstr ""
+
+#. module: share
+#: model:res.groups,comment:share.group_share_user
+msgid ""
+"\n"
+"Members of this groups have access to the sharing wizard, which allows them "
+"to invite external users to view or edit some of their documents."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:853
+#, python-format
+msgid ""
+"The documents have been automatically added to your subscriptions.\n"
+"\n"
+msgstr ""
+
+#. module: share
+#: model:ir.model,name:share.model_res_users
+msgid "Users"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:876
+#, python-format
+msgid ""
+"I've shared %s with you!\n"
+"\n"
+msgstr ""
+
+#. module: share
+#: model:ir.model,name:share.model_res_groups
+msgid "Access Groups"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,invite:0
+msgid "Invite users to OpenSocial record"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:851
+#: code:addons/share/wizard/share_wizard.py:883
+#: field:share.wizard.result.line,password:0
+#, python-format
+msgid "Password"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:77
+#: field:share.wizard,new_users:0
+#, python-format
+msgid "Emails"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,embed_option_search:0
+msgid "Display search view"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,message:0
+msgid "Personal Message"
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:835
+#: code:addons/share/wizard/share_wizard.py:866
+#, python-format
+msgid ""
+"The current user must have an email address configured in User Preferences "
+"to be able to send outgoing emails."
+msgstr ""
+
+#. module: share
+#: code:addons/share/wizard/share_wizard.py:205
+#, python-format
+msgid "No email address configured"
+msgstr ""
+
+#. module: share
+#: field:share.wizard.result.line,login:0
+msgid "Login"
+msgstr ""
+
+#. module: share
+#: view:res.users:0
+msgid "Regular users only (no share user)"
+msgstr ""
+
+#. module: share
+#: field:share.wizard,access_mode:0
+msgid "Access Mode"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "Sharing: preparation"
+msgstr ""
+
+#. module: share
+#: model:ir.model,name:share.model_ir_model_access
+msgid "ir.model.access"
+msgstr ""
+
+#. module: share
+#: view:share.wizard:0
+msgid "or"
+msgstr ""
+
+#. module: share
+#: help:share.wizard,access_mode:0
+msgid "Access rights to be granted on the shared documents."
+msgstr ""
diff --git a/addons/stock/i18n/zh_CN.po b/addons/stock/i18n/zh_CN.po
index 407a76e63b7..add3ef9989b 100644
--- a/addons/stock/i18n/zh_CN.po
+++ b/addons/stock/i18n/zh_CN.po
@@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
-"PO-Revision-Date: 2012-12-09 05:05+0000\n"
-"Last-Translator: sum1201 \n"
+"PO-Revision-Date: 2013-08-06 09:26+0000\n"
+"Last-Translator: Wei \"oldrev\" Li \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-03-16 05:00+0000\n"
-"X-Generator: Launchpad (build 16532)\n"
+"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
+"X-Generator: Launchpad (build 16721)\n"
#. module: stock
#: field:stock.inventory.line.split,line_exist_ids:0
@@ -124,8 +124,8 @@ msgid "Product Moves"
msgstr "产品调拨"
#. module: stock
-#: code:addons/stock/stock.py:2519
-#: code:addons/stock/stock.py:2580
+#: code:addons/stock/stock.py:2524
+#: code:addons/stock/stock.py:2585
#, python-format
msgid "Please provide proper quantity."
msgstr "输入数量不正确"
@@ -251,7 +251,7 @@ msgstr "安排送货单"
#: code:addons/stock/wizard/stock_change_standard_price.py:107
#, python-format
msgid "Active ID is not set in Context."
-msgstr ""
+msgstr "Contex中没有 Active ID"
#. module: stock
#: selection:stock.picking,invoice_state:0
@@ -346,9 +346,9 @@ msgstr "保留复杂的摘要(消息数量,……等)。为了插入到看板视
#. module: stock
#: code:addons/stock/stock.py:768
-#: code:addons/stock/stock.py:2475
-#: code:addons/stock/stock.py:2519
-#: code:addons/stock/stock.py:2580
+#: code:addons/stock/stock.py:2473
+#: code:addons/stock/stock.py:2524
+#: code:addons/stock/stock.py:2585
#: code:addons/stock/wizard/stock_change_product_qty.py:91
#: code:addons/stock/wizard/stock_fill_inventory.py:124
#: code:addons/stock/wizard/stock_inventory_merge.py:43
@@ -476,6 +476,7 @@ msgstr "操作追溯 "
#: selection:stock.move,state:0
#: selection:stock.picking,state:0
#: selection:stock.picking.in,state:0
+#: report:stock.picking.list:0
#: selection:stock.picking.out,state:0
msgid "Waiting Availability"
msgstr "等待可用"
@@ -484,6 +485,7 @@ msgstr "等待可用"
#: selection:report.stock.inventory,state:0
#: selection:report.stock.move,state:0
#: selection:stock.move,state:0
+#: report:stock.picking.list:0
#: view:stock.production.lot:0
#: field:stock.production.lot,stock_available:0
msgid "Available"
@@ -574,7 +576,7 @@ msgstr "实物盘点表草稿"
msgid ""
"Quantities, Units of Measure, Products and Locations cannot be modified on "
"stock moves that have already been processed (except by the Administrator)."
-msgstr ""
+msgstr "已经被处理过的移库单中,数量,计量单位,产品和库位不能修改(除非是 管理员)"
#. module: stock
#: help:report.stock.move,type:0
@@ -614,7 +616,7 @@ msgstr "标签"
#: code:addons/stock/stock.py:1356
#, python-format
msgid "Back order %s has been created ."
-msgstr "未交付清单%s 已经被 创建 ."
+msgstr "延期交货清单%s 已经被 创建 ."
#. module: stock
#: model:ir.model,name:stock.model_report_stock_move
@@ -684,7 +686,7 @@ msgstr "入库单"
#, python-format
msgid ""
"Serial number quantity %d of %s is larger than available quantity (%d)!"
-msgstr ""
+msgstr "系列号数量 %d ( %s ) 大于可用数量(%d)!"
#. module: stock
#: report:stock.picking.list:0
@@ -821,7 +823,7 @@ msgstr "6月"
msgid ""
"Forces to specify a Serial Number for all moves containing this product and "
"coming from a Supplier Location"
-msgstr ""
+msgstr "为包含该产品和来自供应商库位的所有移库单 强制指定一个 系列号"
#. module: stock
#: help:stock.config.settings,group_uos:0
@@ -831,6 +833,8 @@ msgid ""
" For instance, you can sell pieces of meat that you invoice "
"based on their weight."
msgstr ""
+"允许产品的销售和开票的单位不一致.\n"
+" 例如你可以按块卖,但发票按重量开."
#. module: stock
#: field:product.template,property_stock_procurement:0
@@ -866,7 +870,7 @@ msgstr "在进行实时库存核算时,库存调拨处理将自动登入这账
msgid ""
"Please define stock output account for this product or its category: \"%s\" "
"(id: %d)"
-msgstr ""
+msgstr "请为这个产品或者它的类别定义一个出库科目: \"%s\" (id: %d)"
#. module: stock
#: field:stock.picking,message_summary:0
@@ -895,7 +899,7 @@ msgid "Time"
msgstr "时间"
#. module: stock
-#: code:addons/stock/product.py:447
+#: code:addons/stock/product.py:449
#, python-format
msgid "Future Receptions"
msgstr "未来的入库"
@@ -948,7 +952,7 @@ msgstr "入库库位"
msgid ""
"This field is for internal purpose. It is used to decide if the column "
"production lot has to be shown on the moves or not."
-msgstr ""
+msgstr "这 字段是内部用途。用来决定 生产批号这一列 是否显示在移库单上面"
#. module: stock
#: selection:product.product,valuation:0
@@ -982,7 +986,7 @@ msgstr "公司间转移的中途库位"
msgid ""
"You cannot cancel the picking as some moves have been done. You should "
"cancel the picking lines."
-msgstr ""
+msgstr "您不能取消已经有部分移库完成的分拣单。您应该取消 分拣单行"
#. module: stock
#: field:stock.config.settings,decimal_precision:0
@@ -1000,7 +1004,7 @@ msgid "Change Product Quantity"
msgstr "更改产品数量"
#. module: stock
-#: code:addons/stock/product.py:463
+#: code:addons/stock/product.py:465
#, python-format
msgid "Future P&L"
msgstr "未来的产品和库位"
@@ -1027,13 +1031,13 @@ msgstr "盘点库位"
#. module: stock
#: constraint:stock.move:0
msgid "You must assign a serial number for this product."
-msgstr ""
+msgstr "你需要为该产品指定一个系列号"
#. module: stock
#: code:addons/stock/stock.py:2255
#, python-format
msgid "Please define journal on the product category: \"%s\" (id: %d)"
-msgstr ""
+msgstr "请为这个产品类型定义一个分类账: \"%s\" (id: %d)"
#. module: stock
#: view:stock.move:0
@@ -1043,7 +1047,7 @@ msgstr "详情"
#. module: stock
#: selection:stock.picking,state:0
msgid "Ready to Transfer"
-msgstr ""
+msgstr "准备转移"
#. module: stock
#: report:lot.stock.overview:0
@@ -1134,12 +1138,12 @@ msgstr "产品批次"
#. module: stock
#: view:stock.picking:0
msgid "Reverse Transfer"
-msgstr ""
+msgstr "反向转移"
#. module: stock
#: field:stock.config.settings,group_uos:0
msgid "Invoice products in a different unit of measure than the sales order"
-msgstr ""
+msgstr "开票产品的计量单位和销售订单不一致。"
#. module: stock
#: help:stock.location,active:0
@@ -1160,13 +1164,13 @@ msgstr "请在列表视图中选择多个待合并的盘点"
msgid ""
"No products to return (only lines in Done state and not fully returned yet "
"can be returned)!"
-msgstr ""
+msgstr "没有产品被退货(只有完成状态和未完全退货的 行能被退货)!"
#. module: stock
#: view:stock.picking:0
#: view:stock.picking.in:0
msgid "Back Orders"
-msgstr "子单"
+msgstr "延期交货"
#. module: stock
#: field:stock.location,stock_virtual:0
@@ -1208,7 +1212,7 @@ msgstr "生产库位"
#: code:addons/stock/product.py:121
#, python-format
msgid "Please define journal on the product category: \"%s\" (id: %d)."
-msgstr ""
+msgstr "请在产品分类 \"%s\" (id: %d) 上面定义一个分类账。"
#. module: stock
#: field:report.stock.lines.date,date:0
@@ -1267,17 +1271,17 @@ msgstr "建立者"
#, python-format
msgid ""
"You are moving %.2f %s but only %.2f %s available for this serial number."
-msgstr ""
+msgstr "你正在移动 %.2f %s ,但该系列号只有 %.2f %s 可用"
#. module: stock
#: report:stock.picking.list:0
msgid "Internal Shipment :"
-msgstr ""
+msgstr "内部装运:"
#. module: stock
#: view:stock.inventory.line:0
msgid "Split Inventory Line"
-msgstr ""
+msgstr "拆分存货清单行"
#. module: stock
#: selection:stock.location,chained_auto_packing:0
@@ -1298,7 +1302,7 @@ msgid "Supplier"
msgstr "供应商"
#. module: stock
-#: code:addons/stock/stock.py:2859
+#: code:addons/stock/stock.py:2864
#, python-format
msgid ""
"In order to cancel this inventory, you must first unpost related journal "
@@ -1309,7 +1313,7 @@ msgstr "为取消这个盘存,你必须要先取消相关的分录。"
#: code:addons/stock/product.py:113
#, python-format
msgid "Please specify company in Location."
-msgstr ""
+msgstr "请在库位里 指定公司"
#. module: stock
#: view:stock.move:0
@@ -1321,7 +1325,7 @@ msgstr "库存调拨可用(已准备好)"
msgid ""
"Allows you to create and manage your packaging dimensions and types you want "
"to be maintained in your system."
-msgstr ""
+msgstr "允许创建和管理你要在系统中保留的的包装尺寸和类型。"
#. module: stock
#: selection:report.stock.inventory,month:0
@@ -1346,7 +1350,7 @@ msgstr "实物盘点"
#: code:addons/stock/wizard/stock_move.py:214
#, python-format
msgid "Processing Error!"
-msgstr ""
+msgstr "处理错误!"
#. module: stock
#: help:stock.location,chained_company_id:0
@@ -1367,6 +1371,11 @@ msgid ""
"* Available: When products are reserved, it is set to 'Available'.\n"
"* Done: When the shipment is processed, the state is 'Done'."
msgstr ""
+"* 新建: 当移库单被创建还没被确实时。\n"
+"* 等待另外的移库: 当需要等待另外一个移库完成时,出现该状态。例如:链式物流.\n"
+"* 等待可用: 当需求还不能被直接了当地解决时,出现该状态。可能需要等调度程序被运行,有个部件在等待制造等等...\n"
+"* 可用: 当产品已经被保留了,状态就被设置为 可用。\n"
+"* 完成: 当这次装运被处理,状态就是“完成”。"
#. module: stock
#: model:stock.location,name:stock.stock_location_locations_partner
@@ -1423,6 +1432,13 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 创建一个新的凭证。\n"
+"
\n"
+" 根据所执行操作的类型 或者 工人/团队 将要执行的操作,库存凭证系统允许你指定每个库存操作对应到指定的凭证。\n"
+" 例如库存凭证可以是:质量控制,分拣单,包装 等等。\n"
+"
\n"
+" "
#. module: stock
#: help:stock.location,chained_auto_packing:0
@@ -1462,7 +1478,7 @@ msgstr "等待其它调拨"
#: help:stock.change.product.qty,new_quantity:0
msgid ""
"This quantity is expressed in the Default Unit of Measure of the product."
-msgstr ""
+msgstr "此数量以该产品的默认计量单位表示"
#. module: stock
#: help:stock.move,price_unit:0
@@ -1475,7 +1491,7 @@ msgstr "用户在确认分拣时设置期间,字段用于记录产品的成本
#: code:addons/stock/stock.py:1890
#, python-format
msgid "Warning: No Back Order"
-msgstr ""
+msgstr "警告: 没有 延期交货清单"
#. module: stock
#: model:ir.model,name:stock.model_stock_move
@@ -1494,7 +1510,7 @@ msgid "To be refunded/invoiced"
msgstr "已开票或退票"
#. module: stock
-#: code:addons/stock/stock.py:2450
+#: code:addons/stock/stock.py:2448
#, python-format
msgid "You can only delete draft moves."
msgstr "您只能删除草稿状态的调拨。"
@@ -1503,12 +1519,12 @@ msgstr "您只能删除草稿状态的调拨。"
#: code:addons/stock/stock.py:1665
#, python-format
msgid "You cannot move product %s to a location of type view %s."
-msgstr ""
+msgstr "您不能移动产品%s到视图类型的库位%s。"
#. module: stock
#: view:stock.inventory:0
msgid "Split in serial numbers"
-msgstr ""
+msgstr "拆分系列号"
#. module: stock
#: view:stock.move:0
@@ -1537,10 +1553,10 @@ msgid "Additional Info"
msgstr "附加信息"
#. module: stock
-#: code:addons/stock/stock.py:2648
+#: code:addons/stock/stock.py:2653
#, python-format
msgid "Missing partial picking data for move #%s."
-msgstr ""
+msgstr "移库单 #%s 丢失了部分分拣的数据。"
#. module: stock
#: field:stock.location.product,from_date:0
@@ -1631,6 +1647,9 @@ msgstr ""
#. module: stock
#: field:stock.inventory,date:0
#: field:stock.move,create_date:0
+#: field:stock.picking,date:0
+#: field:stock.picking.in,date:0
+#: field:stock.picking.out,date:0
#: field:stock.production.lot,date:0
#: field:stock.tracking,date:0
msgid "Creation Date"
@@ -1731,7 +1750,7 @@ msgstr "上级库位"
#: code:addons/stock/product.py:168
#, python-format
msgid "Please define stock output account for this product: \"%s\" (id: %d)."
-msgstr ""
+msgstr "请为产品 \"%s\" (id: %d) 定义出库科目"
#. module: stock
#: help:stock.location,company_id:0
@@ -1754,15 +1773,15 @@ msgid "Stock Invoice Onshipping"
msgstr "发票未到"
#. module: stock
-#: code:addons/stock/stock.py:2475
+#: code:addons/stock/stock.py:2473
#, python-format
msgid "Please provide a positive quantity to scrap."
-msgstr ""
+msgstr "请提供一个正数给报废库位。"
#. module: stock
#: model:stock.location,name:stock.stock_location_shop1
msgid "Your Company, Birmingham shop"
-msgstr ""
+msgstr "你的公司,伯明翰商店"
#. module: stock
#: view:product.product:0
@@ -1781,7 +1800,7 @@ msgstr "单位成本的币别"
#: selection:stock.picking.in,move_type:0
#: selection:stock.picking.out,move_type:0
msgid "Partial"
-msgstr ""
+msgstr "部分"
#. module: stock
#: selection:report.stock.inventory,month:0
@@ -1792,7 +1811,7 @@ msgstr "9月"
#. module: stock
#: view:product.product:0
msgid "days"
-msgstr ""
+msgstr "天"
#. module: stock
#: model:ir.model,name:stock.model_report_stock_inventory
@@ -1809,13 +1828,13 @@ msgstr "月计划"
#: help:stock.picking.in,origin:0
#: help:stock.picking.out,origin:0
msgid "Reference of the document"
-msgstr ""
+msgstr "文档的参照"
#. module: stock
#: view:stock.picking:0
#: view:stock.picking.in:0
msgid "Is a Back Order"
-msgstr "是子单"
+msgstr "是一个延期交货清单"
#. module: stock
#: report:stock.picking.list:0
@@ -1830,7 +1849,7 @@ msgstr "库存核算科目(出仓)"
#. module: stock
#: view:stock.return.picking.memory:0
msgid "Return Picking Memory"
-msgstr ""
+msgstr "退回分拣存储区"
#. module: stock
#: model:ir.actions.act_window,name:stock.action_move_form2
@@ -1847,7 +1866,7 @@ msgstr "库存调拨"
msgid ""
"Check this option to select existing serial numbers in the list below, "
"otherwise you should enter new ones line by line."
-msgstr ""
+msgstr "点击此项从下面列表中选择一个已有的系列号,否则你要逐行输入新的"
#. module: stock
#: selection:report.stock.move,type:0
@@ -1873,14 +1892,14 @@ msgstr "取消可用"
#: code:addons/stock/wizard/stock_location_product.py:49
#, python-format
msgid "Current Inventory"
-msgstr ""
+msgstr "当前库存清单"
#. module: stock
#: help:product.template,property_stock_production:0
msgid ""
"This stock location will be used, instead of the default one, as the source "
"location for stock moves generated by manufacturing orders."
-msgstr ""
+msgstr "该库位将被使用以替换默认的,同样的,库存移位的源库位将根据制造单生成。"
#. module: stock
#: help:stock.move,date_expected:0
@@ -1903,10 +1922,10 @@ msgid "Shelf 2"
msgstr "货架 2"
#. module: stock
-#: code:addons/stock/stock.py:529
+#: code:addons/stock/stock.py:528
#, python-format
msgid "You cannot remove a lot line."
-msgstr ""
+msgstr "您不能移除一个批号"
#. module: stock
#: help:stock.location,posx:0
@@ -1927,7 +1946,7 @@ msgid "Localization"
msgstr "本地化"
#. module: stock
-#: code:addons/stock/product.py:459
+#: code:addons/stock/product.py:461
#, python-format
msgid "Delivered Qty"
msgstr "已配送数量"
@@ -1935,14 +1954,14 @@ msgstr "已配送数量"
#. module: stock
#: view:stock.partial.picking:0
msgid "Transfer Products"
-msgstr ""
+msgstr "转让产品"
#. module: stock
#: help:product.template,property_stock_inventory:0
msgid ""
"This stock location will be used, instead of the default one, as the source "
"location for stock moves generated when you do an inventory."
-msgstr ""
+msgstr "该库位将被使用以替换默认的,同样当你做盘点时,库存移位的源库位将被生成。"
#. module: stock
#: help:product.template,property_stock_account_output:0
@@ -1998,7 +2017,7 @@ msgstr "日期"
#: field:stock.picking.in,message_is_follower:0
#: field:stock.picking.out,message_is_follower:0
msgid "Is a Follower"
-msgstr ""
+msgstr "是一个关注者"
#. module: stock
#: view:report.stock.inventory:0
@@ -2009,13 +2028,13 @@ msgstr "增加筛选条件"
#. module: stock
#: field:stock.warehouse,lot_stock_id:0
msgid "Location Stock"
-msgstr "库位库存"
+msgstr "库存库位"
#. module: stock
#: code:addons/stock/wizard/stock_partial_picking.py:97
#, python-format
msgid "_Deliver"
-msgstr ""
+msgstr "_Deliver"
#. module: stock
#: code:addons/stock/wizard/stock_inventory_merge.py:64
@@ -2058,7 +2077,7 @@ msgstr "批号"
#: model:ir.actions.act_window,name:stock.action_deliver_move
#: view:product.product:0
msgid "Deliveries"
-msgstr ""
+msgstr "已交付"
#. module: stock
#: model:ir.actions.act_window,help:stock.action_reception_picking_move
@@ -2109,7 +2128,7 @@ msgstr "退回分拣"
#: model:ir.actions.act_window,name:stock.act_stock_return_picking_in
#: model:ir.actions.act_window,name:stock.act_stock_return_picking_out
msgid "Return Shipment"
-msgstr ""
+msgstr "退回件"
#. module: stock
#: model:ir.model,name:stock.model_stock_inventory_merge
@@ -2161,7 +2180,7 @@ msgstr "默认作为您接收当前业务伙伴产品的源库位"
#: sql_constraint:stock.production.lot:0
msgid ""
"The combination of Serial Number and internal reference must be unique !"
-msgstr ""
+msgstr "系列号和内部参考号的组合必须是唯一的"
#. module: stock
#: field:stock.warehouse,partner_id:0
@@ -2179,7 +2198,7 @@ msgstr "调拨分析让您轻松检查和分析您公司的库存调拨.在您
#. module: stock
#: constraint:stock.move:0
msgid "You try to assign a lot which is not from the same product."
-msgstr ""
+msgstr "你试图分配一个不是该产品的批次"
#. module: stock
#: field:report.stock.move,day_diff1:0
@@ -2194,20 +2213,20 @@ msgstr "价格"
#. module: stock
#: field:stock.config.settings,module_stock_invoice_directly:0
msgid "Create and open the invoice when the user finish a delivery order"
-msgstr ""
+msgstr "当用户完成一个发运单后,创建并打开发票"
#. module: stock
#: model:ir.model,name:stock.model_stock_return_picking_memory
msgid "stock.return.picking.memory"
-msgstr ""
+msgstr "stock.return.picking.memory"
#. module: stock
#: field:stock.config.settings,group_stock_inventory_valuation:0
msgid "Generate accounting entries per stock movement"
-msgstr ""
+msgstr "对每个移库动作生成账务分录"
#. module: stock
-#: code:addons/stock/product.py:449
+#: code:addons/stock/product.py:451
#, python-format
msgid "Received Qty"
msgstr "已接收数量"
@@ -2215,7 +2234,7 @@ msgstr "已接收数量"
#. module: stock
#: field:stock.production.lot,ref:0
msgid "Internal Reference"
-msgstr "内部单号"
+msgstr "编号"
#. module: stock
#: help:stock.production.lot,prefix:0
@@ -2229,7 +2248,7 @@ msgstr "显示这序列号时的可选前缀: PREFIX/SERIAL [INT_REF]"
msgid ""
"Forces to specify a Serial Number for all moves containing this product and "
"generated by a Manufacturing Order"
-msgstr ""
+msgstr "强制对包含该产品的所有移库单指定一个系列号,并生成一个制造单"
#. module: stock
#: model:ir.model,name:stock.model_stock_fill_inventory
@@ -2246,7 +2265,7 @@ msgstr "名称"
#. module: stock
#: report:stock.picking.list:0
msgid "Supplier Address :"
-msgstr ""
+msgstr "供应商地址:"
#. module: stock
#: view:stock.inventory.line:0
@@ -2274,28 +2293,28 @@ msgid "Customer Locations"
msgstr "客户库位"
#. module: stock
-#: code:addons/stock/stock.py:2449
-#: code:addons/stock/stock.py:2858
+#: code:addons/stock/stock.py:2448
+#: code:addons/stock/stock.py:2863
#, python-format
msgid "User Error!"
-msgstr ""
+msgstr "用户错误!"
#. module: stock
#: view:stock.partial.picking:0
msgid "Stock partial Picking"
-msgstr ""
+msgstr "库存部分分拣"
#. module: stock
#: view:stock.picking:0
msgid "Create Invoice/Refund"
-msgstr ""
+msgstr "创建发票/退款"
#. module: stock
#: help:stock.picking,message_ids:0
#: help:stock.picking.in,message_ids:0
#: help:stock.picking.out,message_ids:0
msgid "Messages and communication history"
-msgstr ""
+msgstr "消息和通信记录"
#. module: stock
#: view:report.stock.inventory:0
@@ -2320,7 +2339,7 @@ msgstr "供货商库位"
#. module: stock
#: view:stock.location.product:0
msgid "View Products Inventory"
-msgstr ""
+msgstr "查看产品库存"
#. module: stock
#: view:stock.move:0
@@ -2331,7 +2350,7 @@ msgstr "创建"
#: code:addons/stock/stock.py:1776
#, python-format
msgid "Operation forbidden !"
-msgstr ""
+msgstr "操作被禁止"
#. module: stock
#: view:report.stock.inventory:0
@@ -2355,12 +2374,12 @@ msgstr "入库科目"
#. module: stock
#: view:report.stock.move:0
msgid "Shipping type specify, goods coming in or going out"
-msgstr ""
+msgstr "指定运输类型,商品进入或者发出"
#. module: stock
#: view:stock.config.settings:0
msgid "Accounting"
-msgstr ""
+msgstr "会计"
#. module: stock
#: model:ir.ui.menu,name:stock.menu_warehouse_config
@@ -2403,6 +2422,13 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 创建一个库存移库单。\n"
+"
\n"
+" 此菜单给你在一个特定产品上面的库存操作的完整的可追溯性。\n"
+" 你能过滤该产品,查看该产品所有已经发生和未发生的移库。\n"
+"
\n"
+" "
#. module: stock
#: view:stock.location:0
@@ -2429,26 +2455,26 @@ msgstr "计量单位"
#. module: stock
#: field:stock.config.settings,group_stock_multiple_locations:0
msgid "Manage multiple locations and warehouses"
-msgstr ""
+msgstr "管理多库位及仓库"
#. module: stock
#: field:stock.config.settings,group_stock_production_lot:0
msgid "Track serial number on products"
-msgstr ""
+msgstr "追溯产品批次"
#. module: stock
#: field:stock.picking,message_unread:0
#: field:stock.picking.in,message_unread:0
#: field:stock.picking.out,message_unread:0
msgid "Unread Messages"
-msgstr ""
+msgstr "未读消息"
#. module: stock
#: help:stock.production.lot,stock_available:0
msgid ""
"Current quantity of products with this Serial Number available in company "
"warehouses"
-msgstr ""
+msgstr "在 公司仓库中,该序列号的产品当前可用的数量。"
#. module: stock
#: view:stock.inventory:0
@@ -2470,7 +2496,7 @@ msgstr "批次"
#. module: stock
#: view:stock.partial.picking:0
msgid "_Transfer"
-msgstr ""
+msgstr "_Transfer"
#. module: stock
#: selection:report.stock.move,type:0
@@ -2499,7 +2525,7 @@ msgid "Warehouse board"
msgstr "仓库控制台"
#. module: stock
-#: code:addons/stock/product.py:469
+#: code:addons/stock/product.py:471
#, python-format
msgid "Future Qty"
msgstr "未来数量"
@@ -2507,7 +2533,7 @@ msgstr "未来数量"
#. module: stock
#: model:res.groups,name:stock.group_production_lot
msgid "Manage Serial Numbers"
-msgstr ""
+msgstr "管理系列号"
#. module: stock
#: field:stock.move,note:0
@@ -2520,7 +2546,7 @@ msgstr "备注"
#. module: stock
#: selection:stock.picking,state:0
msgid "Transferred"
-msgstr ""
+msgstr "已转运"
#. module: stock
#: report:lot.stock.overview:0
@@ -2541,7 +2567,7 @@ msgstr "移库单类型"
#. module: stock
#: view:stock.move:0
msgid "Process Partially"
-msgstr ""
+msgstr "处理部分"
#. module: stock
#: view:stock.move:0
@@ -2566,7 +2592,7 @@ msgstr "产品"
msgid ""
"Cannot create Journal Entry, Output Account of this product and Valuation "
"account on category of this product are same."
-msgstr ""
+msgstr "不能创建分类账分录,该产品的出库科目和其分类中的 估值科目是一样的"
#. module: stock
#: selection:stock.location,chained_location_type:0
@@ -2606,7 +2632,7 @@ msgstr "出仓产品"
msgid ""
"Please define stock input account for this product or its category: \"%s\" "
"(id: %d)"
-msgstr ""
+msgstr "请为产品 \"%s\" (id: %d) 或者他的类别 定义一个入库科目"
#. module: stock
#: view:report.stock.move:0
@@ -2621,7 +2647,7 @@ msgid "Move"
msgstr "调拨"
#. module: stock
-#: code:addons/stock/product.py:465
+#: code:addons/stock/product.py:467
#, python-format
msgid "P&L Qty"
msgstr "损益数量"
@@ -2629,7 +2655,7 @@ msgstr "损益数量"
#. module: stock
#: model:ir.model,name:stock.model_stock_config_settings
msgid "stock.config.settings"
-msgstr ""
+msgstr "stock.config.settings"
#. module: stock
#: view:stock.production.lot:0
@@ -2652,7 +2678,7 @@ msgstr ""
#. module: stock
#: model:stock.location,name:stock.stock_location_4
msgid "Big Suppliers"
-msgstr ""
+msgstr "大供应商"
#. module: stock
#: model:ir.actions.act_window,help:stock.action_stock_inventory_report
@@ -2678,7 +2704,7 @@ msgstr "创建"
#. module: stock
#: field:stock.change.product.qty,new_quantity:0
msgid "New Quantity on Hand"
-msgstr ""
+msgstr "在手库存调整"
#. module: stock
#: field:stock.move,priority:0
@@ -2728,7 +2754,7 @@ msgstr "取消盘点"
#. module: stock
#: field:stock.config.settings,group_product_variant:0
msgid "Support multiple variants per products "
-msgstr ""
+msgstr "支持每个产品的多个变体 "
#. module: stock
#: code:addons/stock/stock.py:2246
@@ -2736,7 +2762,7 @@ msgstr ""
msgid ""
"Cannot create Journal Entry, Input Account of this product and Valuation "
"account on category of this product are same."
-msgstr ""
+msgstr "不能创建分类账分录,该产品的入库科目和其分类中的 估值科目是一样的"
#. module: stock
#: model:ir.ui.menu,name:stock.menu_stock_unit_measure_stock
@@ -2752,7 +2778,7 @@ msgstr "固定库位"
#. module: stock
#: field:report.stock.inventory,scrap_location:0
msgid "scrap"
-msgstr ""
+msgstr "报废"
#. module: stock
#: code:addons/stock/stock.py:1891
@@ -2760,22 +2786,22 @@ msgstr ""
msgid ""
"By changing the quantity here, you accept the new quantity as complete: "
"OpenERP will not automatically generate a Back Order."
-msgstr ""
+msgstr "通过修改此处的数量,就是您已经收到完成的数量: OpenERP 将自动生成一个延期交货清单。"
#. module: stock
#: report:stock.inventory.move:0
msgid "Manual Quantity"
-msgstr ""
+msgstr "手动数量"
#. module: stock
#: view:product.product:0
msgid "On hand:"
-msgstr ""
+msgstr "在手:"
#. module: stock
#: model:ir.model,name:stock.model_stock_report_prodlots
msgid "Stock report by serial number"
-msgstr ""
+msgstr "按批次统计库库"
#. module: stock
#: selection:report.stock.inventory,month:0
@@ -2801,7 +2827,7 @@ msgstr "显示您产品的最后盘点并用特定的筛选规则去排序。如
#: code:addons/stock/wizard/stock_invoice_onshipping.py:112
#, python-format
msgid "Please create Invoices."
-msgstr ""
+msgstr "请创建发票"
#. module: stock
#: help:stock.config.settings,module_product_expiry:0
@@ -2814,12 +2840,20 @@ msgid ""
" - alert date.\n"
"This installs the module product_expiry."
msgstr ""
+"对产品和序列号跟踪不同的日期。\n"
+"下列日期可被跟踪:\n"
+" -寿命终止\n"
+" -最佳使用日期\n"
+" -搬运日期 \n"
+" -提醒日期\n"
+"\n"
+"这需要安装模块 product_expiry."
#. module: stock
#: code:addons/stock/wizard/stock_partial_picking.py:174
#, python-format
msgid "Please provide proper Quantity."
-msgstr ""
+msgstr "请提供适度的数量"
#. module: stock
#: model:ir.actions.report.xml,name:stock.report_product_history
@@ -2842,7 +2876,7 @@ msgstr "库存账簿"
#: code:addons/stock/wizard/stock_return_picking.py:174
#, python-format
msgid "%s-%s-return"
-msgstr ""
+msgstr "%s-%s-返回"
#. module: stock
#: code:addons/stock/wizard/stock_change_product_qty.py:82
@@ -2859,7 +2893,7 @@ msgstr "强制可用"
#. module: stock
#: field:product.template,loc_rack:0
msgid "Rack"
-msgstr ""
+msgstr "货架"
#. module: stock
#: model:ir.actions.act_window,name:stock.move_scrap
@@ -2870,7 +2904,7 @@ msgstr "损耗调拨"
#. module: stock
#: help:stock.move,prodlot_id:0
msgid "Serial number is used to put a serial number on the production"
-msgstr ""
+msgstr "用作在产品上放置一个系列号"
#. module: stock
#: code:addons/stock/wizard/stock_partial_picking.py:100
@@ -2916,7 +2950,7 @@ msgstr "完成日期"
#: code:addons/stock/stock.py:1663
#, python-format
msgid "You cannot move product %s from a location of type view %s."
-msgstr ""
+msgstr "您不能移动产品%s,因为它来自一个视图类型的库位 %s."
#. module: stock
#: model:stock.location,name:stock.stock_location_company
@@ -2969,6 +3003,13 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 添加一个库位。\n"
+"
\n"
+" 这是你的公司的仓库和库位的结构。\n"
+" 您能点击一个库位,获得产品的清单,和它们在每个库位及其子库位中的库存情况。\n"
+"
\n"
+" "
#. module: stock
#: field:stock.location,stock_real:0
@@ -2978,7 +3019,7 @@ msgstr "实际库存"
#. module: stock
#: field:stock.report.tracklots,tracking_id:0
msgid "Logistic Serial Number"
-msgstr ""
+msgstr "物流系列号"
#. module: stock
#: field:stock.production.lot.revision,date:0
@@ -2998,12 +3039,12 @@ msgstr "批次"
msgid ""
"Total quantity after split exceeds the quantity to split for this product: "
"\"%s\" (id: %d)."
-msgstr ""
+msgstr "产品 \"%s\" (id: %d) 分割后的数量总数超过了这个产品的数量"
#. module: stock
#: view:stock.partial.move.line:0
msgid "Stock Partial Move Line"
-msgstr ""
+msgstr "仓库局部 移库清单"
#. module: stock
#: field:stock.move,product_uos_qty:0
@@ -3025,13 +3066,13 @@ msgstr "联系地址:"
msgid ""
"When you select a serial number on product moves, you can get the upstream "
"or downstream traceability of that product."
-msgstr ""
+msgstr "当您在产品移库单选择了一个系列号,你就能得到该产品的上游或者下游的可追朔性"
#. module: stock
#: code:addons/stock/wizard/stock_partial_picking.py:95
#, python-format
msgid "_Receive"
-msgstr ""
+msgstr "_Receive"
#. module: stock
#: field:stock.incoterms,active:0
@@ -3069,7 +3110,7 @@ msgstr "贸易术语"
#. module: stock
#: model:ir.model,name:stock.model_stock_partial_picking_line
msgid "stock.partial.picking.line"
-msgstr ""
+msgstr "stock.partial.picking.line"
#. module: stock
#: report:lot.stock.overview:0
@@ -3099,6 +3140,14 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 单击为该产品登记一个收货单。\n"
+"
\n"
+" 这里可以找到该产品相关的所有收货记录,\n"
+" 以及所有你正在等待的来自供应商的\n"
+" 尚未到达的收货单。\n"
+"
\n"
+" "
#. module: stock
#: help:stock.fill.inventory,recursive:0
@@ -3111,7 +3160,7 @@ msgstr "如果选择将包括该库位子库位的产品。"
#: help:stock.config.settings,group_stock_inventory_valuation:0
msgid ""
"Allows to configure inventory valuations on products and product categories."
-msgstr ""
+msgstr "允许在产品或者产品分类配置存货估值。"
#. module: stock
#: help:stock.config.settings,module_stock_location:0
@@ -3123,6 +3172,10 @@ msgid ""
"needs, etc.\n"
" This installs the module stock_location."
msgstr ""
+"提供推送物流。这个特性的典型使用是:\n"
+" 管理产品的生产链,管理每个产品的默认库位,,\n"
+" 根据业务需要定义仓库的内路线等等。\n"
+"这需要安装模块 stock_location 。"
#. module: stock
#: view:stock.inventory:0
@@ -3163,7 +3216,7 @@ msgstr "修订版名称"
#. module: stock
#: model:res.groups,name:stock.group_tracking_lot
msgid "Manage Logistic Serial Numbers"
-msgstr ""
+msgstr "管理物流系列号"
#. module: stock
#: view:stock.inventory:0
@@ -3184,12 +3237,19 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 添加一个追踪号码。\n"
+"
\n"
+" 这是所有包装的一个列表。当你选择了一个包装,你\n"
+" 能获得包含在这个包装中的产品的上下游的可追踪性。\n"
+"
\n"
+" "
#. module: stock
-#: code:addons/stock/stock.py:2496
+#: code:addons/stock/stock.py:2501
#, python-format
msgid "%s %s %s has been moved to scrap."
-msgstr ""
+msgstr "%s %s %s 已被 移动到 报废仓."
#. module: stock
#: model:ir.actions.act_window,name:stock.action_picking_tree_out
@@ -3205,6 +3265,7 @@ msgstr "客户包装"
#: selection:stock.move,state:0
#: view:stock.picking:0
#: view:stock.picking.in:0
+#: report:stock.picking.list:0
msgid "Done"
msgstr "完成"
@@ -3242,14 +3303,14 @@ msgstr "如果想生成发票,请在分拣单上填写上业务伙伴."
#. module: stock
#: view:stock.picking.in:0
msgid "Confirm & Receive"
-msgstr ""
+msgstr "确认并接收"
#. module: stock
#: field:stock.picking,origin:0
#: field:stock.picking.in,origin:0
#: field:stock.picking.out,origin:0
msgid "Source Document"
-msgstr ""
+msgstr "源单据"
#. module: stock
#: selection:stock.move,priority:0
@@ -3259,7 +3320,7 @@ msgstr "不紧急"
#. module: stock
#: view:stock.move:0
msgid "Scheduled"
-msgstr ""
+msgstr "排程"
#. module: stock
#: model:ir.actions.act_window,name:stock.action_warehouse_form
@@ -3275,14 +3336,14 @@ msgstr "负责人"
#. module: stock
#: view:stock.move:0
msgid "Process Entirely"
-msgstr ""
+msgstr "处理分录"
#. module: stock
#: help:product.template,property_stock_procurement:0
msgid ""
"This stock location will be used, instead of the default one, as the source "
"location for stock moves generated by procurements."
-msgstr ""
+msgstr "该库位将被使用以替换默认的,同样的,库存移位的源库位将根据需求单生成。"
#. module: stock
#: model:ir.actions.act_window,name:stock.action_stock_inventory_report
@@ -3306,7 +3367,7 @@ msgstr "库存"
#: code:addons/stock/wizard/stock_return_picking.py:222
#, python-format
msgid "Returned Picking"
-msgstr ""
+msgstr "退回的分拣"
#. module: stock
#: model:ir.model,name:stock.model_product_product
@@ -3352,6 +3413,13 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 单击开始一次盘点。\n"
+"
\n"
+" "
+"定期盘点通常用于清点每个库位的商品数量。你可以每年定期使用本功能一次来做总体盘点,也可以在任何你需要的时候进行以便符合当前某个产品的存量级别。\n"
+"
\n"
+" "
#. module: stock
#: view:stock.return.picking:0
@@ -3372,12 +3440,12 @@ msgstr "指定的内部调拨"
#: code:addons/stock/stock.py:790
#, python-format
msgid "You cannot process picking without stock moves."
-msgstr ""
+msgstr "没有移库单,您不能处理分拣。"
#. module: stock
#: field:stock.production.lot,move_ids:0
msgid "Moves for this serial number"
-msgstr ""
+msgstr "该系列号的移库"
#. module: stock
#: field:stock.move,product_uos:0
@@ -3414,7 +3482,7 @@ msgstr "通道(X)"
#. module: stock
#: field:stock.config.settings,group_stock_packaging:0
msgid "Allow to define several packaging methods on products"
-msgstr ""
+msgstr "允许产品定义多种包装方式"
#. module: stock
#: model:stock.location,name:stock.stock_location_7
@@ -3465,7 +3533,7 @@ msgstr "按库位划分产品"
#. module: stock
#: view:stock.config.settings:0
msgid "Logistic"
-msgstr ""
+msgstr "物流"
#. module: stock
#: model:ir.actions.act_window,help:stock.action_location_form
@@ -3511,7 +3579,7 @@ msgstr "货架 1"
#: help:stock.picking.in,date:0
#: help:stock.picking.out,date:0
msgid "Creation time, usually the time of the order."
-msgstr ""
+msgstr "创建时间,通常是下单的时间。"
#. module: stock
#: field:stock.tracking,name:0
@@ -3550,7 +3618,7 @@ msgstr "激活相关科目"
#. module: stock
#: field:stock.location.product,type:0
msgid "Analyse Type"
-msgstr ""
+msgstr "分析类型"
#. module: stock
#: model:ir.actions.act_window,help:stock.action_picking_tree4
@@ -3582,10 +3650,10 @@ msgstr "全部一次性"
#. module: stock
#: model:ir.actions.act_window,name:stock.act_product_stock_move_open
msgid "Inventory Move"
-msgstr ""
+msgstr "库存调拨"
#. module: stock
-#: code:addons/stock/product.py:475
+#: code:addons/stock/product.py:477
#, python-format
msgid "Future Productions"
msgstr "未来生产"
@@ -3615,7 +3683,7 @@ msgstr ""
#: model:ir.actions.act_window,name:stock.action_stock_config_settings
#: view:stock.config.settings:0
msgid "Configure Warehouse"
-msgstr ""
+msgstr "配置仓库"
#. module: stock
#: view:stock.picking:0
@@ -3643,7 +3711,7 @@ msgstr "盘点日期"
#: model:ir.actions.act_window,name:stock.action_receive_move
#: view:product.product:0
msgid "Receptions"
-msgstr ""
+msgstr "接收"
#. module: stock
#: view:report.stock.move:0
@@ -3678,7 +3746,7 @@ msgstr "公司"
#: field:stock.move.consume,product_uom:0
#: field:stock.move.scrap,product_uom:0
msgid "Product Unit of Measure"
-msgstr ""
+msgstr "产品计量单位"
#. module: stock
#: view:stock.move:0
@@ -3712,7 +3780,7 @@ msgstr "盘点批次"
#: field:stock.report.prodlots,prodlot_id:0
#: field:stock.return.picking.memory,prodlot_id:0
msgid "Serial Number"
-msgstr ""
+msgstr "序列号"
#. module: stock
#: model:ir.model,name:stock.model_stock_partial_picking
@@ -3756,7 +3824,7 @@ msgstr ""
#: field:stock.picking.in,message_ids:0
#: field:stock.picking.out,message_ids:0
msgid "Messages"
-msgstr ""
+msgstr "消息"
#. module: stock
#: model:stock.location,name:stock.stock_location_8
@@ -3772,7 +3840,7 @@ msgstr "非欧洲客户"
#: code:addons/stock/product.py:167
#: code:addons/stock/report/report_stock.py:78
#: code:addons/stock/report/report_stock.py:134
-#: code:addons/stock/stock.py:529
+#: code:addons/stock/stock.py:528
#: code:addons/stock/stock.py:790
#: code:addons/stock/stock.py:1190
#: code:addons/stock/stock.py:1199
@@ -3782,7 +3850,8 @@ msgstr "非欧洲客户"
#: code:addons/stock/stock.py:2252
#: code:addons/stock/stock.py:2255
#: code:addons/stock/stock.py:2258
-#: code:addons/stock/stock.py:2585
+#: code:addons/stock/stock.py:2481
+#: code:addons/stock/stock.py:2590
#: code:addons/stock/wizard/stock_fill_inventory.py:59
#: code:addons/stock/wizard/stock_invoice_onshipping.py:112
#: code:addons/stock/wizard/stock_splitinto.py:53
@@ -3798,6 +3867,10 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 单击 定义一个新的仓库。\n"
+"
\n"
+" "
#. module: stock
#: selection:report.stock.inventory,state:0
@@ -3834,12 +3907,12 @@ msgstr "这分拣单不需要开发票。"
msgid ""
"The unit of measure rounding does not allow you to ship \"%s %s\", only "
"roundings of \"%s %s\" is accepted by the Unit of Measure."
-msgstr ""
+msgstr "计量单位取整不允许你的运送 \"%s %s\",只有\"%s %s\" 取整才被该计量单位接受。"
#. module: stock
#: model:stock.location,name:stock.stock_location_shop0
msgid "Your Company, Chicago shop"
-msgstr ""
+msgstr "Your Company, Chicago shop"
#. module: stock
#: selection:report.stock.move,type:0
@@ -3877,12 +3950,12 @@ msgstr ""
msgid ""
"By changing this quantity here, you accept the new quantity as complete: "
"OpenERP will not automatically generate a back order."
-msgstr ""
+msgstr "通过修改此处的数量,就是您已经收到完成的数量: OpenERP 将自动生成一个延期交货清单。"
#. module: stock
#: view:stock.production.lot.revision:0
msgid "Serial Number Revisions"
-msgstr ""
+msgstr "序列号修订版"
#. module: stock
#: model:ir.actions.act_window,name:stock.action_picking_tree
@@ -3900,7 +3973,7 @@ msgstr "送货单已处理"
#. module: stock
#: field:product.template,loc_case:0
msgid "Case"
-msgstr ""
+msgstr "案例"
#. module: stock
#: selection:report.stock.inventory,state:0
@@ -3941,20 +4014,20 @@ msgstr "这选项允许这库位能不能放置废料"
#: field:stock.picking.in,message_follower_ids:0
#: field:stock.picking.out,message_follower_ids:0
msgid "Followers"
-msgstr ""
+msgstr "关注者"
#. module: stock
-#: code:addons/stock/stock.py:2585
+#: code:addons/stock/stock.py:2590
#, python-format
msgid "Cannot consume a move with negative or zero quantity."
-msgstr ""
+msgstr "消耗的移库数量不能小于等于0。"
#. module: stock
#: help:stock.config.settings,decimal_precision:0
msgid ""
"As an example, a decimal precision of 2 will allow weights like: 9.99 kg, "
"whereas a decimal precision of 4 will allow weights like: 0.0231 kg."
-msgstr ""
+msgstr "作为一个例子,十进制精度为2允许重量是这样的:9.99千克,而一个十进制精度为4,将允许重量是这样的,如:0.0231千克。"
#. module: stock
#: view:report.stock.move:0
@@ -3967,7 +4040,7 @@ msgstr "总出库数量"
#: field:stock.picking.in,backorder_id:0
#: field:stock.picking.out,backorder_id:0
msgid "Back Order of"
-msgstr "分单"
+msgstr "之延期交货清单。"
#. module: stock
#: help:stock.partial.move.line,cost:0
@@ -3987,7 +4060,7 @@ msgstr "产品类别"
#. module: stock
#: view:stock.move:0
msgid "Serial Number"
-msgstr ""
+msgstr "序列号"
#. module: stock
#: view:stock.invoice.onshipping:0
@@ -4007,7 +4080,7 @@ msgstr "设置"
#. module: stock
#: model:res.groups,name:stock.group_locations
msgid "Manage Multiple Locations and Warehouses"
-msgstr ""
+msgstr "管理多个库位和仓库"
#. module: stock
#: help:stock.change.standard.price,new_price:0
@@ -4022,10 +4095,10 @@ msgstr ""
"如果成本价减少,库存核算科目为贷方,入库科目为借方。"
#. module: stock
-#: code:addons/stock/stock.py:2811
+#: code:addons/stock/stock.py:2816
#, python-format
msgid "INV:"
-msgstr ""
+msgstr "INV:"
#. module: stock
#: help:stock.config.settings,module_stock_invoice_directly:0
@@ -4034,6 +4107,8 @@ msgid ""
" to be invoiced when you send or deliver goods.\n"
" This installs the module stock_invoice_directly."
msgstr ""
+"当您发送或交付货物,如果交付要开票,将能自动运行开票向导。\n"
+"这需要安装模块 stock_invoice_directly。"
#. module: stock
#: field:stock.location,chained_journal_id:0
@@ -4074,7 +4149,7 @@ msgstr ""
"用于实时库存核算。当设定在一个虚拟库位(而不是内部类型)时,这科目将用于记录从内部库位调拨产品的估值。一般代替这产品的出库科目。它不影响内部库位。"
#. module: stock
-#: code:addons/stock/product.py:457
+#: code:addons/stock/product.py:459
#, python-format
msgid "Future Deliveries"
msgstr "未来送货"
@@ -4100,7 +4175,7 @@ msgstr "自动审核"
#: code:addons/stock/stock.py:1821
#, python-format
msgid "Insufficient Stock for Serial Number !"
-msgstr ""
+msgstr "该序列号的库存不足!"
#. module: stock
#: model:ir.model,name:stock.model_product_template
@@ -4144,13 +4219,13 @@ msgstr "字段用于记录用户确认在分拣期间所用的币别(用于计
#. module: stock
#: help:stock.production.lot,name:0
msgid "Unique Serial Number, will be displayed as: PREFIX/SERIAL [INT_REF]"
-msgstr ""
+msgstr "唯一序列号将被显示为: PREFIX/SERIAL [INT_REF]"
#. module: stock
#: code:addons/stock/product.py:142
#, python-format
msgid "Please define stock input account for this product: \"%s\" (id: %d)."
-msgstr ""
+msgstr "请为产品 \"%s\" (id: %d) 定义一个入库科目。"
#. module: stock
#: model:ir.actions.act_window,name:stock.action_reception_picking_move
@@ -4161,7 +4236,7 @@ msgstr "入库产品"
#. module: stock
#: view:product.product:0
msgid "update"
-msgstr ""
+msgstr "更新"
#. module: stock
#: view:stock.change.product.qty:0
@@ -4180,7 +4255,7 @@ msgstr ""
#: view:stock.return.picking:0
#: view:stock.split.into:0
msgid "or"
-msgstr ""
+msgstr "或"
#. module: stock
#: selection:stock.picking,invoice_state:0
@@ -4232,7 +4307,7 @@ msgstr "自动分拣"
#. module: stock
#: report:stock.picking.list:0
msgid "Customer Address :"
-msgstr ""
+msgstr "客户地址:"
#. module: stock
#: field:stock.location,chained_auto_packing:0
@@ -4288,7 +4363,7 @@ msgstr "你真的要合并盘点?"
#. module: stock
#: view:stock.picking.out:0
msgid "Date of Delivery"
-msgstr ""
+msgstr "发运日期"
#. module: stock
#: field:stock.location,posy:0
@@ -4301,18 +4376,18 @@ msgstr "货架(Y)"
msgid ""
"Please define inventory valuation account on the product category: \"%s\" "
"(id: %d)"
-msgstr ""
+msgstr "请在产品类别 \"%s\" (id: %d) 中定义一个存货估值 科目"
#. module: stock
#: model:ir.model,name:stock.model_stock_production_lot_revision
msgid "Serial Number Revision"
-msgstr ""
+msgstr "序列号版本"
#. module: stock
#: code:addons/stock/product.py:96
#, python-format
msgid "Specify valuation Account for Product Category: %s."
-msgstr ""
+msgstr "为产品分类 %s 指定估值科目。"
#. module: stock
#: help:stock.config.settings,module_claim_from_delivery:0
@@ -4320,12 +4395,14 @@ msgid ""
"Adds a Claim link to the delivery order.\n"
" This installs the module claim_from_delivery."
msgstr ""
+"为发运单添加一个 索赔链接。\n"
+"这需要安装模块claim_from_delivery."
#. module: stock
#: code:addons/stock/wizard/stock_return_picking.py:208
#, python-format
msgid "Please specify at least one non-zero quantity."
-msgstr ""
+msgstr "请指定至少一个非0的数值。"
#. module: stock
#: field:stock.fill.inventory,set_stock_zero:0
@@ -4340,7 +4417,7 @@ msgstr "用户"
#. module: stock
#: field:stock.config.settings,module_stock_location:0
msgid "Create push/pull logistic rules"
-msgstr ""
+msgstr "创建推拉物流规则"
#. module: stock
#: code:addons/stock/wizard/stock_invoice_onshipping.py:98
@@ -4355,7 +4432,7 @@ msgid "November"
msgstr "11月"
#. module: stock
-#: code:addons/stock/product.py:471
+#: code:addons/stock/product.py:473
#, python-format
msgid "Unplanned Qty"
msgstr "计划外数量"
@@ -4379,7 +4456,7 @@ msgstr "1月"
#. module: stock
#: constraint:stock.move:0
msgid "You cannot move products from or to a location of the type view."
-msgstr ""
+msgstr "不能将视图类型的库位作为移库单的源库位或者目标库位"
#. module: stock
#: help:stock.config.settings,group_stock_production_lot:0
@@ -4387,14 +4464,14 @@ msgid ""
"This allows you to manage products by using serial numbers. When you select "
"a serial number on product moves, you can get the upstream or downstream "
"traceability of that product."
-msgstr ""
+msgstr "允许你使用序列号管理产品。当你在产品移库时选择了一个序列号,你能获得对产品的上游和下游的可追溯性。"
#. module: stock
#: code:addons/stock/wizard/stock_fill_inventory.py:124
#, python-format
msgid ""
"No product in this location. Please select a location in the product form."
-msgstr ""
+msgstr "该库位没有产品。请从产品表单选择一个库位。"
#. module: stock
#: model:ir.actions.act_window,name:stock.act_product_stock_move_futur_open
@@ -4407,7 +4484,7 @@ msgid "Move History (parent moves)"
msgstr "调拨日志(上级调拨)"
#. module: stock
-#: code:addons/stock/product.py:453
+#: code:addons/stock/product.py:455
#, python-format
msgid "Future Stock"
msgstr "未来库存"
@@ -4446,6 +4523,12 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 单击新增一个产品的发货单。\n"
+"
\n"
+" 你在此处会发现该产品相关的所有调拨历史,以及所有你需要发货给客户的产品。\n"
+"
\n"
+" "
#. module: stock
#: model:ir.actions.act_window,name:stock.action_location_form
@@ -4462,7 +4545,7 @@ msgstr "库位"
msgid ""
"If this shipment was split, then this field links to the shipment which "
"contains the already processed part."
-msgstr ""
+msgstr "如果这个装运单被分割,该字段将连接到包括了已经处理的部分的装运单。"
#. module: stock
#: view:stock.inventory:0
@@ -4481,7 +4564,7 @@ msgstr "前缀"
#: view:stock.move:0
#: view:stock.move.split:0
msgid "Split in Serial Numbers"
-msgstr ""
+msgstr "分开进系列号"
#. module: stock
#: help:product.template,property_stock_account_input:0
@@ -4524,12 +4607,12 @@ msgstr "指定包装的属性如数量,类型等"
#. module: stock
#: view:product.product:0
msgid "Delays"
-msgstr ""
+msgstr "延迟"
#. module: stock
#: report:stock.picking.list:0
msgid "Schedule Date"
-msgstr ""
+msgstr "预定日期"
#. module: stock
#: field:stock.location.product,to_date:0
@@ -4580,7 +4663,7 @@ msgstr "5月"
#: code:addons/stock/product.py:110
#, python-format
msgid "No difference between standard price and new price!"
-msgstr ""
+msgstr "标准价和新价格之间没有不同!"
#. module: stock
#: view:stock.picking.out:0
@@ -4595,7 +4678,7 @@ msgstr "送货"
#. module: stock
#: view:stock.fill.inventory:0
msgid "Import the current inventory"
-msgstr ""
+msgstr "导入当前库存清单"
#. module: stock
#: model:ir.actions.act_window,name:stock.action5
@@ -4609,7 +4692,7 @@ msgid "Location Content"
msgstr "库位包含"
#. module: stock
-#: code:addons/stock/product.py:477
+#: code:addons/stock/product.py:479
#, python-format
msgid "Produced Qty"
msgstr "产品数量"
@@ -4631,7 +4714,7 @@ msgstr "连锁库位类型"
#: help:stock.picking.in,min_date:0
#: help:stock.picking.out,min_date:0
msgid "Scheduled time for the shipment to be processed"
-msgstr ""
+msgstr "该装运单被处理的计划时间"
#. module: stock
#: selection:report.stock.inventory,location_type:0
@@ -4676,19 +4759,19 @@ msgstr ""
#. module: stock
#: view:stock.picking.in:0
msgid "Date of Reception"
-msgstr ""
+msgstr "接收的日期"
#. module: stock
#: help:stock.config.settings,group_stock_multiple_locations:0
msgid ""
"This allows to configure and use multiple stock locations and warehouses,\n"
" instead of having a single default one."
-msgstr ""
+msgstr "允许配置和使用多个库位和仓库,替换默认的一个。"
#. module: stock
#: view:stock.picking:0
msgid "Confirm & Transfer"
-msgstr ""
+msgstr "确认并传递"
#. module: stock
#: field:stock.location,scrap_location:0
@@ -4734,6 +4817,12 @@ msgid ""
" \n"
" "
msgstr ""
+"\n"
+" 点击以创建一个送货单. \n"
+"
\n"
+" 这里是所有送货单的列表,它们是根据不同的销售订单和物流规则 被准备好的。\n"
+"
\n"
+" "
#. module: stock
#: help:stock.tracking,name:0
@@ -4799,7 +4888,7 @@ msgstr "准备处理"
#. module: stock
#: report:stock.picking.list:0
msgid "Warehouse Address :"
-msgstr ""
+msgstr "仓库地址:"
#~ msgid "["
#~ msgstr "["