@@ -0,0 +1,9 | |||
|
1 | class AddRolesTimeEntriesVisibility < ActiveRecord::Migration | |
|
2 | def self.up | |
|
3 | add_column :roles, :time_entries_visibility, :string, :limit => 30, :default => 'all', :null => false | |
|
4 | end | |
|
5 | ||
|
6 | def self.down | |
|
7 | remove_column :roles, :time_entries_visibility | |
|
8 | end | |
|
9 | end |
@@ -143,7 +143,7 class ProjectsController < ApplicationController | |||
|
143 | 143 | @open_issues_by_tracker = Issue.visible.open.where(cond).group(:tracker).count |
|
144 | 144 | @total_issues_by_tracker = Issue.visible.where(cond).group(:tracker).count |
|
145 | 145 | |
|
146 |
if User.current.allowed_to?( |
|
|
146 | if User.current.allowed_to_view_all_time_entries?(@project) | |
|
147 | 147 | @total_hours = TimeEntry.visible.where(cond).sum(:hours).to_f |
|
148 | 148 | end |
|
149 | 149 |
@@ -39,6 +39,11 class Role < ActiveRecord::Base | |||
|
39 | 39 | ['own', :label_issues_visibility_own] |
|
40 | 40 | ] |
|
41 | 41 | |
|
42 | TIME_ENTRIES_VISIBILITY_OPTIONS = [ | |
|
43 | ['all', :label_time_entries_visibility_all], | |
|
44 | ['own', :label_time_entries_visibility_own] | |
|
45 | ] | |
|
46 | ||
|
42 | 47 | USERS_VISIBILITY_OPTIONS = [ |
|
43 | 48 | ['all', :label_users_visibility_all], |
|
44 | 49 | ['members_of_visible_projects', :label_users_visibility_members_of_visible_projects] |
@@ -75,6 +80,9 class Role < ActiveRecord::Base | |||
|
75 | 80 | validates_inclusion_of :users_visibility, |
|
76 | 81 | :in => USERS_VISIBILITY_OPTIONS.collect(&:first), |
|
77 | 82 | :if => lambda {|role| role.respond_to?(:users_visibility) && role.users_visibility_changed?} |
|
83 | validates_inclusion_of :time_entries_visibility, | |
|
84 | :in => TIME_ENTRIES_VISIBILITY_OPTIONS.collect(&:first), | |
|
85 | :if => lambda {|role| role.respond_to?(:time_entries_visibility) && role.time_entries_visibility_changed?} | |
|
78 | 86 | |
|
79 | 87 | # Copies attributes from another role, arg can be an id or a Role |
|
80 | 88 | def copy_from(arg, options={}) |
@@ -46,7 +46,7 class TimeEntry < ActiveRecord::Base | |||
|
46 | 46 | |
|
47 | 47 | scope :visible, lambda {|*args| |
|
48 | 48 | joins(:project). |
|
49 |
where( |
|
|
49 | where(TimeEntry.visible_condition(args.shift || User.current, *args)) | |
|
50 | 50 | } |
|
51 | 51 | scope :on_issue, lambda {|issue| |
|
52 | 52 | joins(:issue). |
@@ -55,6 +55,32 class TimeEntry < ActiveRecord::Base | |||
|
55 | 55 | |
|
56 | 56 | safe_attributes 'hours', 'comments', 'project_id', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields' |
|
57 | 57 | |
|
58 | # Returns a SQL conditions string used to find all time entries visible by the specified user | |
|
59 | def self.visible_condition(user, options={}) | |
|
60 | Project.allowed_to_condition(user, :view_time_entries, options) do |role, user| | |
|
61 | if role.time_entries_visibility == 'all' | |
|
62 | nil | |
|
63 | elsif role.time_entries_visibility == 'own' && user.id && user.logged? | |
|
64 | "#{table_name}.user_id = #{user.id}" | |
|
65 | else | |
|
66 | '1=0' | |
|
67 | end | |
|
68 | end | |
|
69 | end | |
|
70 | ||
|
71 | # Returns true if user or current user is allowed to view the time entry | |
|
72 | def visible?(user=nil) | |
|
73 | (user || User.current).allowed_to?(:view_time_entries, self.project) do |role, user| | |
|
74 | if role.time_entries_visibility == 'all' | |
|
75 | true | |
|
76 | elsif role.time_entries_visibility == 'own' | |
|
77 | self.user == user | |
|
78 | else | |
|
79 | false | |
|
80 | end | |
|
81 | end | |
|
82 | end | |
|
83 | ||
|
58 | 84 | def initialize(attributes=nil, *args) |
|
59 | 85 | super |
|
60 | 86 | if new_record? && self.activity.nil? |
@@ -116,7 +142,9 class TimeEntry < ActiveRecord::Base | |||
|
116 | 142 | |
|
117 | 143 | # Returns true if the time entry can be edited by usr, otherwise false |
|
118 | 144 | def editable_by?(usr) |
|
119 | (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project) | |
|
145 | visible?(usr) && ( | |
|
146 | (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project) | |
|
147 | ) | |
|
120 | 148 | end |
|
121 | 149 | |
|
122 | 150 | # Returns the custom_field_values that can be edited by the given user |
@@ -634,6 +634,12 class User < Principal | |||
|
634 | 634 | allowed_to?(action, nil, options.reverse_merge(:global => true), &block) |
|
635 | 635 | end |
|
636 | 636 | |
|
637 | def allowed_to_view_all_time_entries?(context) | |
|
638 | allowed_to?(:view_time_entries, context) do |role, user| | |
|
639 | role.time_entries_visibility == 'all' | |
|
640 | end | |
|
641 | end | |
|
642 | ||
|
637 | 643 | # Returns true if the user is allowed to delete the user's own account |
|
638 | 644 | def own_account_deletable? |
|
639 | 645 | Setting.unsubscribe? && |
@@ -62,7 +62,7 | |||
|
62 | 62 | rows.right l(:field_estimated_hours), issue_estimated_hours_details(@issue), :class => 'estimated-hours' |
|
63 | 63 | end |
|
64 | 64 | end |
|
65 |
if User.current.allowed_to |
|
|
65 | if User.current.allowed_to_view_all_time_entries?(@project) | |
|
66 | 66 | if @issue.total_spent_hours > 0 |
|
67 | 67 | rows.right l(:label_spent_time), issue_spent_hours_details(@issue), :class => 'spent-time' |
|
68 | 68 | end |
@@ -1,6 +1,8 | |||
|
1 | <% if @total_hours.present? %> | |
|
1 | <% if User.current.allowed_to?(:view_time_entries, @project) %> | |
|
2 | 2 | <h3><%= l(:label_spent_time) %></h3> |
|
3 | <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p> | |
|
3 | <% if @total_hours.present? %> | |
|
4 | <p><span class="icon icon-time"><%= l_hours(@total_hours) %></span></p> | |
|
5 | <% end %> | |
|
4 | 6 | <p> |
|
5 | 7 | <% if User.current.allowed_to?(:log_time, @project) %> |
|
6 | 8 | <%= link_to l(:button_log_time), new_project_time_entry_path(@project) %> | |
@@ -10,6 +10,10 | |||
|
10 | 10 | <p><%= f.select :issues_visibility, Role::ISSUES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p> |
|
11 | 11 | <% end %> |
|
12 | 12 | |
|
13 | <% unless @role.anonymous? %> | |
|
14 | <p><%= f.select :time_entries_visibility, Role::TIME_ENTRIES_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p> | |
|
15 | <% end %> | |
|
16 | ||
|
13 | 17 | <p><%= f.select :users_visibility, Role::USERS_VISIBILITY_OPTIONS.collect {|v| [l(v.last), v.first]} %></p> |
|
14 | 18 | |
|
15 | 19 | <% if @role.new_record? && @roles.any? %> |
@@ -19,7 +19,7 | |||
|
19 | 19 | <th><%= l(:field_estimated_hours) %></th> |
|
20 | 20 | <td class="total-hours"><%= html_hours(l_hours(@version.estimated_hours)) %></td> |
|
21 | 21 | </tr> |
|
22 |
<% if User.current.allowed_to |
|
|
22 | <% if User.current.allowed_to_view_all_time_entries?(@project) %> | |
|
23 | 23 | <tr> |
|
24 | 24 | <th><%= l(:label_spent_time) %></th> |
|
25 | 25 | <td class="total-hours"><%= html_hours(l_hours(@version.spent_hours)) %></td> |
@@ -341,6 +341,7 en: | |||
|
341 | 341 | field_must_change_passwd: Must change password at next logon |
|
342 | 342 | field_default_status: Default status |
|
343 | 343 | field_users_visibility: Users visibility |
|
344 | field_time_entries_visibility: Time logs visibility | |
|
344 | 345 | |
|
345 | 346 | setting_app_title: Application title |
|
346 | 347 | setting_app_subtitle: Application subtitle |
@@ -361,6 +361,7 fr: | |||
|
361 | 361 | field_must_change_passwd: Doit changer de mot de passe à la prochaine connexion |
|
362 | 362 | field_default_status: Statut par défaut |
|
363 | 363 | field_users_visibility: Visibilité des utilisateurs |
|
364 | field_time_entries_visibility: Visibilité du temps passé | |
|
364 | 365 | |
|
365 | 366 | setting_app_title: Titre de l'application |
|
366 | 367 | setting_app_subtitle: Sous-titre de l'application |
@@ -962,6 +963,8 fr: | |||
|
962 | 963 | label_disable_notifications: Désactiver les notifications |
|
963 | 964 | label_blank_value: non renseigné |
|
964 | 965 | label_parent_task_attributes: Attributs des tâches parentes |
|
966 | label_time_entries_visibility_all: Tous les temps passés | |
|
967 | label_time_entries_visibility_own: Ses propres temps passés | |
|
965 | 968 | |
|
966 | 969 | button_login: Connexion |
|
967 | 970 | button_submit: Soumettre |
@@ -27,6 +27,38 class TimeEntryTest < ActiveSupport::TestCase | |||
|
27 | 27 | :groups_users, |
|
28 | 28 | :enabled_modules |
|
29 | 29 | |
|
30 | def test_visibility_with_permission_to_view_all_time_entries | |
|
31 | user = User.generate! | |
|
32 | role = Role.generate!(:permissions => [:view_time_entries], :time_entries_visibility => 'all') | |
|
33 | Role.non_member.remove_permission! :view_time_entries | |
|
34 | project = Project.find(1) | |
|
35 | User.add_to_project user, project, role | |
|
36 | own = TimeEntry.generate! :user => user, :project => project | |
|
37 | other = TimeEntry.generate! :user => User.find(2), :project => project | |
|
38 | ||
|
39 | assert TimeEntry.visible(user).find_by_id(own.id) | |
|
40 | assert TimeEntry.visible(user).find_by_id(other.id) | |
|
41 | ||
|
42 | assert own.visible?(user) | |
|
43 | assert other.visible?(user) | |
|
44 | end | |
|
45 | ||
|
46 | def test_visibility_with_permission_to_view_own_time_entries | |
|
47 | user = User.generate! | |
|
48 | role = Role.generate!(:permissions => [:view_time_entries], :time_entries_visibility => 'own') | |
|
49 | Role.non_member.remove_permission! :view_time_entries | |
|
50 | project = Project.find(1) | |
|
51 | User.add_to_project user, project, role | |
|
52 | own = TimeEntry.generate! :user => user, :project => project | |
|
53 | other = TimeEntry.generate! :user => User.find(2), :project => project | |
|
54 | ||
|
55 | assert TimeEntry.visible(user).find_by_id(own.id) | |
|
56 | assert_nil TimeEntry.visible(user).find_by_id(other.id) | |
|
57 | ||
|
58 | assert own.visible?(user) | |
|
59 | assert_equal false, other.visible?(user) | |
|
60 | end | |
|
61 | ||
|
30 | 62 | def test_hours_format |
|
31 | 63 | assertions = { "2" => 2.0, |
|
32 | 64 | "21.1" => 21.1, |
General Comments 0
You need to be logged in to leave comments.
Login now