diff --git a/addons/hr/hr.py b/addons/hr/hr.py
index 467f34f708b..e4253f38762 100644
--- a/addons/hr/hr.py
+++ b/addons/hr/hr.py
@@ -221,7 +221,7 @@ class hr_employee(osv.osv):
(model, mail_group_id) = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'group_all_employees')
employee = self.browse(cr, uid, employee_id, context=context)
self.pool.get('mail.group').message_post(cr, uid, [mail_group_id],
- body='Welcome to %s! Please help them take the first steps with OpenERP!' % (employee.name),
+ body='Welcome to %s! Please help him/her take the first steps with OpenERP!' % (employee.name),
subtype='mail.mt_comment', context=context)
except:
pass # group deleted: do not push a message
diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py
index 4e113a23510..b1593091e4c 100644
--- a/addons/hr_holidays/hr_holidays.py
+++ b/addons/hr_holidays/hr_holidays.py
@@ -180,7 +180,8 @@ class hr_holidays(osv.osv):
]
_sql_constraints = [
- ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))", "The employee or employee category of this request is missing."),
+ ('type_value', "CHECK( (holiday_type='employee' AND employee_id IS NOT NULL) or (holiday_type='category' AND category_id IS NOT NULL))",
+ "The employee or employee category of this request is missing. Please make sure that your user login is linked to an employee."),
('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be anterior to the end date."),
('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."),
]
diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml
index b531677d538..716c1cba8d5 100644
--- a/addons/hr_recruitment/hr_recruitment_view.xml
+++ b/addons/hr_recruitment/hr_recruitment_view.xml
@@ -41,7 +41,7 @@
-
+
@@ -199,7 +199,7 @@
-
+
diff --git a/addons/l10n_be/fiscal_templates.xml b/addons/l10n_be/fiscal_templates.xml
index 5e26a7e67c1..5cf5a21381b 100644
--- a/addons/l10n_be/fiscal_templates.xml
+++ b/addons/l10n_be/fiscal_templates.xml
@@ -22,12 +22,6 @@
-
-
-
-
-
-
diff --git a/addons/l10n_be_coda/wizard/account_coda_import.py b/addons/l10n_be_coda/wizard/account_coda_import.py
index 9465980629c..8aabe601bc8 100644
--- a/addons/l10n_be_coda/wizard/account_coda_import.py
+++ b/addons/l10n_be_coda/wizard/account_coda_import.py
@@ -128,6 +128,8 @@ class account_coda_import(osv.osv_memory):
raise osv.except_osv(_('Error') + ' R1004', _("No matching Bank Account (with Account Journal) found.\n\nPlease set-up a Bank Account with as Account Number '%s' and as Currency '%s' and an Account Journal.") % (statement['acc_number'], statement['currency']))
statement['description'] = rmspaces(line[90:125])
statement['balance_start'] = float(rmspaces(line[43:58])) / 1000
+ if line[42] == '1': #1 = Debit, the starting balance is negative
+ statement['balance_start'] = - statement['balance_start']
statement['balance_start_date'] = time.strftime(tools.DEFAULT_SERVER_DATE_FORMAT, time.strptime(rmspaces(line[58:64]), '%d%m%y'))
statement['accountHolder'] = rmspaces(line[64:90])
statement['paperSeqNumber'] = rmspaces(line[2:5])
diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index 2185f372caa..ee7aced2660 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -906,6 +906,7 @@ class pos_order(osv.osv):
account_tax_obj = self.pool.get('account.tax')
user_proxy = self.pool.get('res.users')
property_obj = self.pool.get('ir.property')
+ cur_obj = self.pool.get('res.currency')
period = account_period_obj.find(cr, uid, context=context)[0]
@@ -1001,17 +1002,19 @@ class pos_order(osv.osv):
#TOFIX: a deep refactoring of this method (and class!) is needed in order to get rid of this stupid hack
assert order.lines, _('The POS order must have lines when calling this method')
# Create an move for each order line
+
+ cur = order.pricelist_id.currency_id
for line in order.lines:
tax_amount = 0
taxes = [t for t in line.product_id.taxes_id]
computed_taxes = account_tax_obj.compute_all(cr, uid, taxes, line.price_unit * (100.0-line.discount) / 100.0, line.qty)['taxes']
for tax in computed_taxes:
- tax_amount += round(tax['amount'], 2)
+ tax_amount += cur_obj.round(cr, uid, cur, tax['amount'])
group_key = (tax['tax_code_id'], tax['base_code_id'], tax['account_collected_id'], tax['id'])
group_tax.setdefault(group_key, 0)
- group_tax[group_key] += round(tax['amount'], 2)
+ group_tax[group_key] += cur_obj.round(cr, uid, cur, tax['amount'])
amount = line.price_subtotal
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 660434ba53e..98ba49c8856 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -1,24 +1,8 @@
function openerp_pos_models(instance, module){ //module is instance.point_of_sale
var QWeb = instance.web.qweb;
- // rounds a value with a fixed number of decimals.
- // round(3.141492,2) -> 3.14
- function round(value,decimals){
- var mult = Math.pow(10,decimals || 0);
- return Math.round(value*mult)/mult;
- }
- window.round = round;
-
- // rounds a value with decimal form precision
- // round(3.141592,0.025) ->3.125
- function round_pr(value,precision){
- if(!precision || precision < 0){
- throw new Error('round_pr(): needs a precision greater than zero, got '+precision+' instead');
- }
- return Math.round(value / precision) * precision;
- }
- window.round_pr = round_pr;
-
+ var round_di = instance.web.round_decimals;
+ var round_pr = instance.web.round_precision
// The PosModel contains the Point Of Sale's representation of the backend.
// Since the PoS must work in standalone ( Without connection to the server )
@@ -391,7 +375,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
var quant = Math.max(parseFloat(quantity) || 0, 0);
var unit = this.get_unit();
if(unit){
- this.quantity = Math.max(unit.rounding, Math.round(quant / unit.rounding) * unit.rounding);
+ this.quantity = Math.max(unit.rounding, round_pr(quant, unit.rounding));
this.quantityStr = this.quantity.toFixed(Math.max(0,Math.ceil(Math.log(1.0 / unit.rounding) / Math.log(10))));
}else{
this.quantity = quant;
@@ -484,7 +468,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
},
// changes the base price of the product for this orderline
set_unit_price: function(price){
- this.price = round(parseFloat(price) || 0, 2);
+ this.price = round_di(parseFloat(price) || 0, 2);
this.trigger('change');
},
get_unit_price: function(){
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index eca4a3c5915..0bd399428ed 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -983,7 +983,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
}
if(this.pos_widget.action_bar){
- this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0);
+ this.pos_widget.action_bar.set_button_disabled('validation', remaining > 0.000001);
}
},
set_numpad_state: function(numpadState) {
diff --git a/addons/portal_project/__init__.py b/addons/portal_project/__init__.py
index 26c654db9dd..164c7826e47 100644
--- a/addons/portal_project/__init__.py
+++ b/addons/portal_project/__init__.py
@@ -19,3 +19,4 @@
#
##############################################################################
+import project
diff --git a/addons/portal_project/project.py b/addons/portal_project/project.py
new file mode 100644
index 00000000000..fb783c8a531
--- /dev/null
+++ b/addons/portal_project/project.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# 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.osv import osv
+
+
+class portal_project(osv.Model):
+ """ Update of mail_mail class, to add the signin URL to notifications. """
+ _inherit = 'project.project'
+
+ def _get_visibility_selection(self, cr, uid, context=None):
+ """ Override to add portal option. """
+ selection = super(portal_project, self)._get_visibility_selection(cr, uid, context=context)
+ idx = [item[0] for item in selection].index('public')
+ selection.insert((idx + 1), ('portal', 'Portal Users and Employees'))
+ return selection
+ # return [('public', 'All Users'),
+ # ('portal', 'Portal Users and Employees'),
+ # ('employees', 'Employees Only'),
+ # ('followers', 'Followers Only')]
diff --git a/addons/portal_project/security/ir.model.access.csv b/addons/portal_project/security/ir.model.access.csv
index 0e743050568..a999f5555d9 100644
--- a/addons/portal_project/security/ir.model.access.csv
+++ b/addons/portal_project/security/ir.model.access.csv
@@ -5,3 +5,9 @@ access_task_type,task_type,project.model_project_task_type,portal.group_portal,1
access_task_work,task_work,project.model_project_task_work,portal.group_portal,1,0,0,0
access_project_category,project_category,project.model_project_category,portal.group_portal,1,0,0,0
access_account_analytic_account,account_analytic_account,analytic.model_account_analytic_account,portal.group_portal,1,0,0,0
+access_project_anonymous,project,project.model_project_project,portal.group_anonymous,1,0,0,0
+access_task_anonymous,task,project.model_project_task,portal.group_anonymous,1,0,0,0
+access_task_type_anonymous,task_type,project.model_project_task_type,portal.group_anonymous,1,0,0,0
+access_task_work_anonymous,task_work,project.model_project_task_work,portal.group_anonymous,1,0,0,0
+access_project_category_anonymous,project_category,project.model_project_category,portal.group_anonymous,1,0,0,0
+access_account_analytic_account_anonymous,account_analytic_account,analytic.model_account_analytic_account,portal.group_anonymous,1,0,0,0
\ No newline at end of file
diff --git a/addons/portal_project/security/portal_security.xml b/addons/portal_project/security/portal_security.xml
index 0d995cfd70b..8d58ed70aeb 100644
--- a/addons/portal_project/security/portal_security.xml
+++ b/addons/portal_project/security/portal_security.xml
@@ -2,19 +2,68 @@
+
+ Project: employees: public, portal, employees or following
+ ['|',
+ ('privacy_visibility', 'in', ['public', 'portal', 'employees']),
+ '&',
+ ('privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
+ ]
+
+
+
- Portal Projects
-
- ['|', ('privacy_visibility', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id])]
+ Project: portal users: public, portal or following
+
+ ['|',
+ ('privacy_visibility', 'in', ['public', 'portal']),
+ '&',
+ ('privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
+ ]
+
+ Project: anonymous users: public only
+
+ [('privacy_visibility', '=', 'public')]
+
+
+
+
+ Project/Task: employees: public, portal, employee or following or assigned
+ ['|',
+ ('user_id', '=', user.id),
+ '|',
+ ('project_id.privacy_visibility', 'in', ['public', 'portal', 'employees']),
+ '&',
+ ('project_id.privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
+ ]
+
+
- Portal Tasks
-
- [('message_follower_ids','in', [user.partner_id.id])]
+ Project/Task: portal users: public or portal and following
+
+ ['|',
+ ('project_id.privacy_visibility', '=', 'public'),
+ '&',
+ ('project_id.privacy_visibility', 'in', ['portal', 'followers']),
+ '|',
+ ('message_follower_ids','in', [user.partner_id.id]),
+ ('user_id', '=', user.id),
+ ]
+
+ Project/Task: anonymous users: public only
+
+ [('project_id.privacy_visibility', '=', 'public')]
+
+
+
diff --git a/addons/portal_project/tests/__init__.py b/addons/portal_project/tests/__init__.py
new file mode 100644
index 00000000000..c79181f7710
--- /dev/null
+++ b/addons/portal_project/tests/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Business Applications
+# Copyright (c) 2012-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_access_rights
+
+checks = [
+ test_access_rights,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/portal_project/tests/test_access_rights.py b/addons/portal_project/tests/test_access_rights.py
new file mode 100644
index 00000000000..a7d56cb3895
--- /dev/null
+++ b/addons/portal_project/tests/test_access_rights.py
@@ -0,0 +1,300 @@
+# -*- 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.osv.orm import except_orm
+from openerp.tests import common
+from openerp.tools import mute_logger
+
+
+class TestPortalProject(common.TransactionCase):
+
+ def setUp(self):
+ super(TestPortalProject, self).setUp()
+ cr, uid = self.cr, self.uid
+
+ # Useful models
+ self.project_project = self.registry('project.project')
+ self.project_task = self.registry('project.task')
+ self.res_users = self.registry('res.users')
+ self.res_partner = self.registry('res.partner')
+
+ # Find Employee group
+ group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
+ self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
+
+ # Find Project User group
+ group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
+ self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
+
+ # Find Project Manager group
+ group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
+ self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
+
+ # Find Portal group
+ group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
+ self.group_portal_id = group_portal_ref and group_portal_ref[1] or False
+
+ # Find Anonymous group
+ group_anonymous_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_anonymous')
+ self.group_anonymous_id = group_anonymous_ref and group_anonymous_ref[1] or False
+
+ # Test users to use through the various tests
+ self.user_alfred_id = self.res_users.create(cr, uid, {
+ 'name': 'Alfred EmployeeUser',
+ 'login': 'alfred',
+ 'alias_name': 'alfred',
+ 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
+ })
+ self.user_bert_id = self.res_users.create(cr, uid, {
+ 'name': 'Bert Nobody',
+ 'login': 'bert',
+ 'alias_name': 'bert',
+ 'groups_id': [(6, 0, [])]
+ })
+ self.user_chell_id = self.res_users.create(cr, uid, {
+ 'name': 'Chell Portal',
+ 'login': 'chell',
+ 'alias_name': 'chell',
+ 'groups_id': [(6, 0, [self.group_portal_id])]
+ })
+ self.user_donovan_id = self.res_users.create(cr, uid, {
+ 'name': 'Donovan Anonymous',
+ 'login': 'donovan',
+ 'alias_name': 'donovan',
+ 'groups_id': [(6, 0, [self.group_anonymous_id])]
+ })
+ self.user_ernest_id = self.res_users.create(cr, uid, {
+ 'name': 'Ernest Manager',
+ 'login': 'ernest',
+ 'alias_name': 'ernest',
+ 'groups_id': [(6, 0, [self.group_project_manager_id])]
+ })
+
+ # Test 'Pigs' project
+ self.project_pigs_id = self.project_project.create(cr, uid,
+ {'name': 'Pigs', 'privacy_visibility': 'public'},
+ {'mail_create_nolog': True})
+ # Various test tasks
+ self.task_1_id = self.project_task.create(cr, uid,
+ {'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
+ {'mail_create_nolog': True})
+ self.task_2_id = self.project_task.create(cr, uid,
+ {'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
+ {'mail_create_nolog': True})
+ self.task_3_id = self.project_task.create(cr, uid,
+ {'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
+ {'mail_create_nolog': True})
+ self.task_4_id = self.project_task.create(cr, uid,
+ {'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
+ {'mail_create_nolog': True})
+ self.task_5_id = self.project_task.create(cr, uid,
+ {'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
+ {'mail_create_nolog': True})
+ self.task_6_id = self.project_task.create(cr, uid,
+ {'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
+ {'mail_create_nolog': True})
+
+ @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 """
+ cr, uid, pigs_id = self.cr, self.uid, self.project_pigs_id
+
+ # ----------------------------------------
+ # CASE1: public project
+ # ----------------------------------------
+
+ # Do: Alfred reads project -> ok (employee ok public)
+ self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ # Test: all project tasks visible
+ task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: project user cannot see all tasks of a public project')
+ # Test: all project tasks readable
+ self.project_task.read(cr, self.user_alfred_id, task_ids, ['name'])
+ # Test: all project tasks writable
+ self.project_task.write(cr, self.user_alfred_id, task_ids, {'description': 'TestDescription'})
+
+ # Do: Bert reads project -> crash, no group
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_bert_id, pigs_id, ['name'])
+ # Test: no project task visible
+ self.assertRaises(except_orm, self.project_task.search,
+ cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ # Test: no project task readable
+ self.assertRaises(except_orm, self.project_task.read,
+ cr, self.user_bert_id, task_ids, ['name'])
+ # Test: no project task writable
+ self.assertRaises(except_orm, self.project_task.write,
+ cr, self.user_bert_id, task_ids, {'description': 'TestDescription'})
+
+ # Do: Chell reads project -> ok (portal ok public)
+ self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ # Test: all project tasks visible
+ task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: project user cannot see all tasks of a public project')
+ # Test: all project tasks readable
+ self.project_task.read(cr, self.user_chell_id, task_ids, ['name'])
+ # Test: no project task writable
+ self.assertRaises(except_orm, self.project_task.write,
+ cr, self.user_chell_id, task_ids, {'description': 'TestDescription'})
+
+ # Do: Donovan reads project -> ok (anonymous ok public)
+ self.project_project.read(cr, self.user_donovan_id, pigs_id, ['name'])
+ # Test: all project tasks visible
+ task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: anonymous user cannot see all tasks of a public project')
+ # Test: all project tasks readable
+ self.project_task.read(cr, self.user_donovan_id, task_ids, ['name'])
+ # Test: no project task writable
+ self.assertRaises(except_orm, self.project_task.write,
+ cr, self.user_donovan_id, task_ids, {'description': 'TestDescription'})
+
+ # ----------------------------------------
+ # CASE2: portal project
+ # ----------------------------------------
+ self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'portal'})
+
+ # Do: Alfred reads project -> ok (employee ok public)
+ self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ # Test: all project tasks visible
+ task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: project user cannot see all tasks of a portal project')
+
+ # Do: Bert reads project -> crash, no group
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_bert_id, pigs_id, ['name'])
+ # Test: no project task searchable
+ self.assertRaises(except_orm, self.project_task.search,
+ cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+
+ # Data: task follower
+ self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
+
+ # Do: Chell reads project -> ok (portal ok public)
+ self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ # Test: only followed project tasks visible + assigned
+ task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: portal user should see the followed tasks of a portal project')
+
+ # Do: Donovan reads project -> ko (anonymous ko portal)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_donovan_id, pigs_id, ['name'])
+ # Test: no project task visible
+ task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a portal project')
+
+ # Data: task follower cleaning
+ self.project_task.message_unsubscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
+
+ # ----------------------------------------
+ # CASE3: employee project
+ # ----------------------------------------
+ self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'employees'})
+
+ # Do: Alfred reads project -> ok (employee ok employee)
+ self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ # Test: all project tasks visible
+ task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: project user cannot see all tasks of an employees project')
+
+ # Do: Bert reads project -> crash, no group
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_bert_id, pigs_id, ['name'])
+
+ # Do: Chell reads project -> ko (portal ko employee)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_chell_id, pigs_id, ['name'])
+ # Test: no project task visible + assigned
+ task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ self.assertFalse(task_ids, 'access rights: portal user should not see tasks of an employees project, even if assigned')
+
+ # Do: Donovan reads project -> ko (anonymous ko employee)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_donovan_id, pigs_id, ['name'])
+ # Test: no project task visible
+ task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of an employees project')
+
+ # ----------------------------------------
+ # CASE4: followers project
+ # ----------------------------------------
+ self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'followers'})
+
+ # Do: Alfred reads project -> ko (employee ko followers)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_alfred_id, pigs_id, ['name'])
+ # Test: no project task visible
+ task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_4_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
+
+ # Do: Bert reads project -> crash, no group
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_bert_id, pigs_id, ['name'])
+
+ # Do: Chell reads project -> ko (portal ko employee)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_chell_id, pigs_id, ['name'])
+ # Test: no project task visible
+ task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_5_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
+
+ # Do: Donovan reads project -> ko (anonymous ko employee)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_donovan_id, pigs_id, ['name'])
+ # Test: no project task visible
+ task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a followers project')
+
+ # Data: subscribe Alfred, Chell and Donovan as follower
+ self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
+ self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id, self.user_alfred_id])
+
+ # Do: Alfred reads project -> ok (follower ok followers)
+ self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ # Test: followed + assigned tasks visible
+ task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_1_id, self.task_3_id, self.task_4_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: employee user should not see followed + assigned tasks of a follower project')
+
+ # Do: Chell reads project -> ok (follower ok follower)
+ self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ # Test: followed + assigned tasks visible
+ task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
+ self.assertEqual(set(task_ids), test_task_ids,
+ 'access rights: employee user should not see followed + assigned tasks of a follower project')
+
+ # Do: Donovan reads project -> ko (anonymous ko follower even if follower)
+ self.assertRaises(except_orm, self.project_project.read,
+ cr, self.user_donovan_id, pigs_id, ['name'])
diff --git a/addons/portal_project_issue/security/ir.model.access.csv b/addons/portal_project_issue/security/ir.model.access.csv
index 80a38366b51..7355f7a9b1d 100644
--- a/addons/portal_project_issue/security/ir.model.access.csv
+++ b/addons/portal_project_issue/security/ir.model.access.csv
@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_issues,project_issue,project_issue.model_project_issue,portal.group_portal,1,0,0,0
access_case_section,crm_case_section,crm.model_crm_case_section,portal.group_portal,1,0,0,0
+access_issues_anonymous,project_issue,project_issue.model_project_issue,portal.group_anonymous,1,0,0,0
\ No newline at end of file
diff --git a/addons/portal_project_issue/security/portal_security.xml b/addons/portal_project_issue/security/portal_security.xml
index a121eeb593e..f6cd8d89be7 100644
--- a/addons/portal_project_issue/security/portal_security.xml
+++ b/addons/portal_project_issue/security/portal_security.xml
@@ -3,14 +3,36 @@
- Portal Personal Issues
-
- [('message_follower_ids','in', [user.partner_id.id])]
+ Project/Issue: portal users: public or portal and following
+
+ ['|',
+ ('project_id.privacy_visibility', '=', 'public'),
+ '&',
+ ('project_id.privacy_visibility', 'in', ['portal', 'followers']),
+ '|',
+ ('message_follower_ids','in', [user.partner_id.id]),
+ ('user_id', '=', user.id),
+ ]
-
-
-
-
+
+
+
+ Project/Issue: employees: public, portal, employee or following or assigned
+ ['|',
+ ('user_id', '=', user.id),
+ '|',
+ ('project_id.privacy_visibility', 'in', ['public', 'portal', 'employees']),
+ '&',
+ ('project_id.privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
+ ]
+
+
+
+ Project/Issue: anonymous users: public only
+
+ [('project_id.privacy_visibility', '=', 'public')]
+
diff --git a/addons/portal_project_issue/tests/__init__.py b/addons/portal_project_issue/tests/__init__.py
new file mode 100644
index 00000000000..fa5b47ac535
--- /dev/null
+++ b/addons/portal_project_issue/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_access_rights
+
+checks = [
+ test_access_rights,
+]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/portal_project_issue/tests/test_access_rights.py b/addons/portal_project_issue/tests/test_access_rights.py
new file mode 100644
index 00000000000..28d337a4965
--- /dev/null
+++ b/addons/portal_project_issue/tests/test_access_rights.py
@@ -0,0 +1,185 @@
+# -*- 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.portal_project.tests.test_access_rights import TestPortalProject
+from openerp.osv.orm import except_orm
+from openerp.tools import mute_logger
+
+
+class TestPortalIssueProject(TestPortalProject):
+
+ def setUp(self):
+ super(TestPortalIssueProject, 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})
+
+ @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 """
+ cr, uid, pigs_id = self.cr, self.uid, self.project_pigs_id
+
+ # ----------------------------------------
+ # CASE1: public project
+ # ----------------------------------------
+
+ # 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)])
+ 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')
+ # Test: all project issues readable
+ self.project_issue.read(cr, self.user_alfred_id, issue_ids, ['name'])
+ # Test: all project issues writable
+ self.project_issue.write(cr, self.user_alfred_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)])
+ # Test: no project issue readable
+ self.assertRaises(except_orm, self.project_issue.read,
+ cr, self.user_bert_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'})
+
+ # 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)])
+ self.assertEqual(set(issue_ids), test_issue_ids,
+ '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'])
+ # Test: no project issue writable
+ self.assertRaises(except_orm, self.project_issue.write,
+ cr, self.user_chell_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)])
+ self.assertEqual(set(issue_ids), test_issue_ids,
+ 'access rights: project user cannot see all issues of a public project')
+
+ # ----------------------------------------
+ # CASE2: portal project
+ # ----------------------------------------
+ self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'portal'})
+
+ # 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)])
+ self.assertEqual(set(issue_ids), test_issue_ids,
+ '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)])
+
+ # 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])
+
+ # 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)])
+ 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')
+
+ # 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])
+
+ # ----------------------------------------
+ # CASE3: employee project
+ # ----------------------------------------
+ self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'employees'})
+
+ # 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)])
+ 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')
+
+ # 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)])
+ self.assertFalse(issue_ids, 'access rights: portal user should not see issues of an employees project, even if assigned')
+
+ # ----------------------------------------
+ # CASE4: followers project
+ # ----------------------------------------
+ self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'followers'})
+
+ # 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)])
+ 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')
+
+ # 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)])
+ 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')
+
+ # 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])
+
+ # 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)])
+ 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')
+
+ # 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)])
+ 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')
diff --git a/addons/portal_project_long_term/security/ir.model.access.csv b/addons/portal_project_long_term/security/ir.model.access.csv
index 738c264a71c..efbc4fc2418 100644
--- a/addons/portal_project_long_term/security/ir.model.access.csv
+++ b/addons/portal_project_long_term/security/ir.model.access.csv
@@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_issues,project_phase,project_long_term.model_project_phase,portal.group_portal,1,0,0,0
+access_issues_anonymous,project_phase_anonymous,project_long_term.model_project_phase,portal.group_anonymous,1,0,0,0
diff --git a/addons/portal_project_long_term/security/portal_security.xml b/addons/portal_project_long_term/security/portal_security.xml
index b46d277e4ee..eab70ebe734 100644
--- a/addons/portal_project_long_term/security/portal_security.xml
+++ b/addons/portal_project_long_term/security/portal_security.xml
@@ -3,14 +3,24 @@
- Portal Personal Long term project
-
- ['|',('project_id.message_follower_ids','in', [user.partner_id.id]),('task_ids.message_follower_ids','in', [user.partner_id.id])]
+ Project/Phase: portal users: public or portal and following
+
+ ['|',
+ ('project_id.privacy_visibility', '=', 'public'),
+ '&',
+ ('project_id.privacy_visibility', 'in', ['portal', 'followers']),
+ '|',
+ ('message_follower_ids','in', [user.partner_id.id]),
+ ('user_id', '=', user.id),
+ ]
-
-
-
-
+
+
+
+ Project/Phase: anonymous users: public only
+
+ [('project_id.privacy_visibility', '=', 'public')]
+
diff --git a/addons/project/project.py b/addons/project/project.py
index eb328007d44..0b99356a9de 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -208,6 +208,12 @@ class project(osv.osv):
"""Overriden in project_issue to offer more options"""
return [('project.task', "Tasks")]
+ def _get_visibility_selection(self, cr, uid, context=None):
+ """ Overriden in portal_project to offer more options """
+ return [('public', 'All Users'),
+ ('employees', 'Employees Only'),
+ ('followers', 'Followers Only')]
+
def attachment_tree_view(self, cr, uid, ids, context):
task_ids = self.pool.get('project.task').search(cr, uid, [('project_id', 'in', ids)])
domain = [
@@ -229,6 +235,8 @@ class project(osv.osv):
}
# Lambda indirection method to avoid passing a copy of the overridable method when declaring the field
_alias_models = lambda self, *args, **kwargs: self._get_alias_models(*args, **kwargs)
+ _visibility_selection = lambda self, *args, **kwargs: self._get_visibility_selection(*args, **kwargs)
+
_columns = {
'complete_name': fields.function(_complete_name, string="Project Name", type='char', size=250),
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the project without removing it."),
@@ -267,7 +275,7 @@ class project(osv.osv):
"with Tasks (or optionally Issues if the Issue Tracker module is installed)."),
'alias_model': fields.selection(_alias_models, "Alias Model", select=True, required=True,
help="The kind of document created when an email is received on this project's email alias"),
- 'privacy_visibility': fields.selection([('public','All Users'), ('followers','Followers Only')], 'Privacy / Visibility', required=True),
+ 'privacy_visibility': fields.selection(_visibility_selection, 'Privacy / Visibility', required=True),
'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'), ('cancelled', 'Cancelled'),('pending','Pending'),('close','Closed')], 'Status', required=True,),
'doc_count':fields.function(_get_attached_docs, string="Number of documents attached", type='int')
}
diff --git a/addons/project/security/project_security.xml b/addons/project/security/project_security.xml
index f1d8c7e760f..4bbaa9b788e 100644
--- a/addons/project/security/project_security.xml
+++ b/addons/project/security/project_security.xml
@@ -38,39 +38,62 @@
- Project multi-company
+ Project: multi-company
- ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+ ['|',
+ ('company_id', '=', False),
+ ('company_id', 'child_of', [user.company_id.id]),
+ ]
+
+
+
+ Project: project manager: see all
+
+ [(1, '=', 1)]
+
- Public Members
+ Project: employees: public, employees or followers
-
- ['|',('privacy_visibility','in',[False,'public']),('message_follower_ids','in',[user.partner_id.id])]
+ ['|',
+ ('privacy_visibility', 'in', ['public', 'employees']),
+ ('message_follower_ids', 'in', [user.partner_id.id])
+ ]
+
- Task multi-company
+ Project/Task: multi-company
- ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]
+ ['|',
+ ('company_id', '=', False),
+ ('company_id', 'child_of', [user.company_id.id]),
+ ]
- Tasks According to User and Project
+ Project/Task: employees: public or employee or following or assigned
-
- ['|','|','|',('user_id','=',False),('user_id','=',user.id),('project_id.members','in', [user.id]),('project_id.user_id','=',user.id)]
-
+ ['|',
+ ('user_id', '=', user.id),
+ '|',
+ ('project_id.privacy_visibility', 'in', ['public', 'employees']),
+ '&',
+ ('project_id.privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.partner_id.id]),
+ ]
+
- Project Managers: all tasks from all projects
+ Project/Task: project manager: see all
- [(1,'=',1)]
+ [(1, '=', 1)]
+
diff --git a/addons/project_issue/security/project_issue_security.xml b/addons/project_issue/security/project_issue_security.xml
index c3119073a75..5215a94a8b7 100644
--- a/addons/project_issue/security/project_issue_security.xml
+++ b/addons/project_issue/security/project_issue_security.xml
@@ -1,5 +1,27 @@
+
+
+ Project/Issue: project manager: see all
+
+ [(1, '=', 1)]
+
+
+
+
+ Project/Issue: employees: public or employee or following or assigned
+
+ ['|',
+ '|',
+ ('project_id.privacy_visibility', 'in', ['public', 'employees']),
+ '&',
+ ('project_id.privacy_visibility', '=', 'followers'),
+ ('message_follower_ids', 'in', [user.id]),
+ ('user_id', '=', user.id),
+ ]
+
+
+