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), + ] + + +