redmine_rate 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/COPYRIGHT.txt +18 -0
  2. data/CREDITS.txt +5 -0
  3. data/GPL.txt +339 -0
  4. data/README.rdoc +93 -0
  5. data/Rakefile +37 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/rate_caches_controller.rb +35 -0
  8. data/app/controllers/rates_controller.rb +154 -0
  9. data/app/models/rate.rb +161 -0
  10. data/app/views/rate_caches/index.html.erb +21 -0
  11. data/app/views/rates/_form.html.erb +36 -0
  12. data/app/views/rates/_list.html.erb +42 -0
  13. data/app/views/rates/create.js.rjs +2 -0
  14. data/app/views/rates/create_error.js.rjs +5 -0
  15. data/app/views/rates/edit.html.erb +3 -0
  16. data/app/views/rates/index.html.erb +5 -0
  17. data/app/views/rates/new.html.erb +3 -0
  18. data/app/views/rates/show.html.erb +23 -0
  19. data/app/views/users/_membership_rate.html.erb +23 -0
  20. data/app/views/users/_rates.html.erb +17 -0
  21. data/assets/images/database_refresh.png +0 -0
  22. data/config/locales/de.yml +18 -0
  23. data/config/locales/en.yml +18 -0
  24. data/config/locales/fr.yml +20 -0
  25. data/config/locales/ru.yml +9 -0
  26. data/config/routes.rb +4 -0
  27. data/init.rb +49 -0
  28. data/lang/de.yml +9 -0
  29. data/lang/en.yml +9 -0
  30. data/lang/fr.yml +8 -0
  31. data/lib/rate_conversion.rb +16 -0
  32. data/lib/rate_memberships_hook.rb +15 -0
  33. data/lib/rate_project_hook.rb +106 -0
  34. data/lib/rate_sort_helper_patch.rb +102 -0
  35. data/lib/rate_time_entry_patch.rb +66 -0
  36. data/lib/rate_users_helper_patch.rb +37 -0
  37. data/lib/redmine_rate/hooks/plugin_timesheet_view_timesheets_report_header_tags_hook.rb +11 -0
  38. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_group_header_hook.rb +9 -0
  39. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_hook.rb +18 -0
  40. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_sum_hook.rb +17 -0
  41. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheets_time_entry_row_class_hook.rb +21 -0
  42. data/lib/redmine_rate/hooks/timesheet_hook_helper.rb +14 -0
  43. data/lib/redmine_rate/hooks/view_layouts_base_html_head_hook.rb +9 -0
  44. data/lib/tasks/cache.rake +13 -0
  45. data/lib/tasks/data.rake +163 -0
  46. data/rails/init.rb +1 -0
  47. data/test/functional/rates_controller_test.rb +401 -0
  48. data/test/integration/admin_panel_test.rb +81 -0
  49. data/test/integration/routing_test.rb +16 -0
  50. data/test/test_helper.rb +43 -0
  51. data/test/unit/lib/rate_time_entry_patch_test.rb +77 -0
  52. data/test/unit/lib/rate_users_helper_patch_test.rb +37 -0
  53. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_view_timesheets_report_header_tags_hook_test.rb +26 -0
  54. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_group_header_hook_test.rb +26 -0
  55. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_hook_test.rb +47 -0
  56. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_sum_hook_test.rb +48 -0
  57. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheets_time_entry_row_class_hook_test.rb +60 -0
  58. data/test/unit/rate_for_test.rb +74 -0
  59. data/test/unit/rate_test.rb +333 -0
  60. metadata +137 -0
@@ -0,0 +1,154 @@
1
+ class RatesController < ApplicationController
2
+ unloadable
3
+ helper :users
4
+ helper :sort
5
+ include SortHelper
6
+
7
+ before_filter :require_admin
8
+ before_filter :require_user_id, :only => [:index, :new]
9
+ before_filter :set_back_url
10
+
11
+ ValidSortOptions = {'date_in_effect' => "#{Rate.table_name}.date_in_effect", 'project_id' => "#{Project.table_name}.name"}
12
+
13
+ # GET /rates?user_id=1
14
+ # GET /rates.xml?user_id=1
15
+ def index
16
+ sort_init "#{Rate.table_name}.date_in_effect", "desc"
17
+ sort_update ValidSortOptions
18
+
19
+ @rates = Rate.history_for_user(@user, sort_clause)
20
+
21
+ respond_to do |format|
22
+ format.html { render :action => 'index', :layout => !request.xhr?}
23
+ format.xml { render :xml => @rates }
24
+ end
25
+ end
26
+
27
+ # GET /rates/1
28
+ # GET /rates/1.xml
29
+ def show
30
+ @rate = Rate.find(params[:id])
31
+
32
+ respond_to do |format|
33
+ format.html # show.html.erb
34
+ format.xml { render :xml => @rate }
35
+ end
36
+ end
37
+
38
+ # GET /rates/new?user_id=1
39
+ # GET /rates/new.xml?user_id=1
40
+ def new
41
+ @rate = Rate.new(:user_id => @user.id)
42
+
43
+ respond_to do |format|
44
+ format.html # new.html.erb
45
+ format.xml { render :xml => @rate }
46
+ end
47
+ end
48
+
49
+ # GET /rates/1/edit
50
+ def edit
51
+ @rate = Rate.find(params[:id])
52
+ end
53
+
54
+ # POST /rates
55
+ # POST /rates.xml
56
+ def create
57
+ @rate = Rate.new(params[:rate])
58
+
59
+ respond_to do |format|
60
+ if @rate.save
61
+ format.html {
62
+ flash[:notice] = 'Rate was successfully created.'
63
+ redirect_back_or_default(rates_url(:user_id => @rate.user_id))
64
+ }
65
+ format.xml { render :xml => @rate, :status => :created, :location => @rate }
66
+ format.js { render :action => 'create.js.rjs'}
67
+ else
68
+ format.html { render :action => "new" }
69
+ format.xml { render :xml => @rate.errors, :status => :unprocessable_entity }
70
+ format.js {
71
+ flash.now[:error] = 'Error creating a new Rate.'
72
+ render :action => 'create_error.js.rjs'
73
+ }
74
+ end
75
+ end
76
+ end
77
+
78
+ # PUT /rates/1
79
+ # PUT /rates/1.xml
80
+ def update
81
+ @rate = Rate.find(params[:id])
82
+
83
+ respond_to do |format|
84
+ # Locked rates will fail saving here.
85
+ if @rate.update_attributes(params[:rate])
86
+ flash[:notice] = 'Rate was successfully updated.'
87
+ format.html { redirect_back_or_default(rates_url(:user_id => @rate.user_id)) }
88
+ format.xml { head :ok }
89
+ else
90
+ if @rate.locked?
91
+ flash[:error] = "Rate is locked and cannot be edited"
92
+ @rate.reload # Removes attribute changes
93
+ end
94
+ format.html { render :action => "edit" }
95
+ format.xml { render :xml => @rate.errors, :status => :unprocessable_entity }
96
+ end
97
+ end
98
+ end
99
+
100
+ # DELETE /rates/1
101
+ # DELETE /rates/1.xml
102
+ def destroy
103
+ @rate = Rate.find(params[:id])
104
+ @rate.destroy
105
+
106
+ respond_to do |format|
107
+ format.html {
108
+ flash[:error] = "Rate is locked and cannot be deleted" if @rate.locked?
109
+ redirect_back_or_default(rates_url(:user_id => @rate.user_id))
110
+ }
111
+ format.xml { head :ok }
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def require_user_id
118
+ begin
119
+ @user = User.find(params[:user_id])
120
+ rescue ActiveRecord::RecordNotFound
121
+ respond_to do |format|
122
+ flash[:error] = l(:rate_error_user_not_found)
123
+ format.html { redirect_to(home_url) }
124
+ format.xml { render :xml => "User not found", :status => :not_found }
125
+ end
126
+ end
127
+ end
128
+
129
+ def set_back_url
130
+ @back_url = params[:back_url]
131
+ @back_url
132
+ end
133
+
134
+ # Override defination from ApplicationController to make sure it follows a
135
+ # whitelist
136
+ def redirect_back_or_default(default)
137
+ whitelist = %r{(rates|/users/edit)}
138
+
139
+ back_url = CGI.unescape(params[:back_url].to_s)
140
+ if !back_url.blank?
141
+ begin
142
+ uri = URI.parse(back_url)
143
+ if uri.path && uri.path.match(whitelist)
144
+ super
145
+ return
146
+ end
147
+ rescue URI::InvalidURIError
148
+ # redirect to default
149
+ logger.debug("Invalid URI sent to redirect_back_or_default: " + params[:back_url].inspect)
150
+ end
151
+ end
152
+ redirect_to default
153
+ end
154
+ end
@@ -0,0 +1,161 @@
1
+ require 'lockfile'
2
+
3
+ class Rate < ActiveRecord::Base
4
+ unloadable
5
+ class InvalidParameterException < Exception; end
6
+ CACHING_LOCK_FILE_NAME = 'rate_cache'
7
+
8
+ belongs_to :project
9
+ belongs_to :user
10
+ has_many :time_entries
11
+
12
+ validates_presence_of :user_id
13
+ validates_presence_of :date_in_effect
14
+ validates_numericality_of :amount
15
+
16
+ before_save :unlocked?
17
+ after_save :update_time_entry_cost_cache
18
+ before_destroy :unlocked?
19
+ after_destroy :update_time_entry_cost_cache
20
+
21
+ named_scope :history_for_user, lambda { |user, order|
22
+ {
23
+ :conditions => { :user_id => user.id },
24
+ :order => order,
25
+ :include => :project
26
+ }
27
+ }
28
+
29
+ def locked?
30
+ return self.time_entries.length > 0
31
+ end
32
+
33
+ def unlocked?
34
+ return !self.locked?
35
+ end
36
+
37
+ def default?
38
+ return self.project.nil?
39
+ end
40
+
41
+ def specific?
42
+ return !self.default?
43
+ end
44
+
45
+ def update_time_entry_cost_cache
46
+ TimeEntry.update_cost_cache(user, project)
47
+ end
48
+
49
+ # API to find the Rate for a +user+ on a +project+ at a +date+
50
+ def self.for(user, project = nil, date = Date.today.to_s)
51
+ # Check input since it's a "public" API
52
+ if Object.const_defined? 'Group' # 0.8.x compatibility
53
+ raise Rate::InvalidParameterException.new("user must be a Principal instance") unless user.is_a?(Principal)
54
+ else
55
+ raise Rate::InvalidParameterException.new("user must be a User instance") unless user.is_a?(User)
56
+ end
57
+ raise Rate::InvalidParameterException.new("project must be a Project instance") unless project.nil? || project.is_a?(Project)
58
+ Rate.check_date_string(date)
59
+
60
+ rate = self.for_user_project_and_date(user, project, date)
61
+ # Check for a default (non-project) rate
62
+ rate = self.default_for_user_and_date(user, date) if rate.nil? && project
63
+ rate
64
+ end
65
+
66
+ # API to find the amount for a +user+ on a +project+ at a +date+
67
+ def self.amount_for(user, project = nil, date = Date.today.to_s)
68
+ rate = self.for(user, project, date)
69
+
70
+ return nil if rate.nil?
71
+ return rate.amount
72
+ end
73
+
74
+ def self.update_all_time_entries_with_missing_cost(options={})
75
+ with_common_lockfile(options[:force]) do
76
+ TimeEntry.all(:conditions => {:cost => nil}).each do |time_entry|
77
+ begin
78
+ time_entry.save_cached_cost
79
+ rescue Rate::InvalidParameterException => ex
80
+ puts "Error saving #{time_entry.id}: #{ex.message}"
81
+ end
82
+ end
83
+ end
84
+ store_cache_timestamp('last_caching_run', Time.now.utc.to_s)
85
+ end
86
+
87
+ def self.update_all_time_entries_to_refresh_cache(options={})
88
+ with_common_lockfile(options[:force]) do
89
+ TimeEntry.find_each do |time_entry| # batch find
90
+ begin
91
+ time_entry.save_cached_cost
92
+ rescue Rate::InvalidParameterException => ex
93
+ puts "Error saving #{time_entry.id}: #{ex.message}"
94
+ end
95
+ end
96
+ end
97
+ store_cache_timestamp('last_cache_clearing_run', Time.now.utc.to_s)
98
+ end
99
+
100
+ private
101
+ def self.for_user_project_and_date(user, project, date)
102
+ if project.nil?
103
+ return Rate.find(:first,
104
+ :order => 'date_in_effect DESC',
105
+ :conditions => [
106
+ "user_id IN (?) AND date_in_effect <= ? AND project_id IS NULL",
107
+ user.id,
108
+ date
109
+ ])
110
+
111
+ else
112
+ return Rate.find(:first,
113
+ :order => 'date_in_effect DESC',
114
+ :conditions => [
115
+ "user_id IN (?) AND project_id IN (?) AND date_in_effect <= ?",
116
+ user.id,
117
+ project.id,
118
+ date
119
+ ])
120
+ end
121
+ end
122
+
123
+ def self.default_for_user_and_date(user, date)
124
+ self.for_user_project_and_date(user, nil, date)
125
+ end
126
+
127
+ # Checks a date string to make sure it is in format of +YYYY-MM-DD+, throwing
128
+ # a Rate::InvalidParameterException otherwise
129
+ def self.check_date_string(date)
130
+ raise Rate::InvalidParameterException.new("date must be a valid Date string (e.g. YYYY-MM-DD)") unless date.is_a?(String)
131
+
132
+ begin
133
+ Date.parse(date)
134
+ rescue ArgumentError
135
+ raise Rate::InvalidParameterException.new("date must be a valid Date string (e.g. YYYY-MM-DD)")
136
+ end
137
+ end
138
+
139
+ def self.store_cache_timestamp(cache_name, timestamp)
140
+ Setting.plugin_redmine_rate = Setting.plugin_redmine_rate.merge({cache_name => timestamp})
141
+ end
142
+
143
+ def self.with_common_lockfile(force = false, &block)
144
+ # Wait 1 second after stealing a forced lock
145
+ options = {:retries => 0, :suspend => 1}
146
+ options[:max_age] = 1 if force
147
+
148
+ Lockfile(lock_file, options) do
149
+ block.call
150
+ end
151
+ end
152
+
153
+ if Rails.env.test?
154
+ public
155
+ generator_for :date_in_effect => Date.today
156
+ end
157
+
158
+ def self.lock_file
159
+ Rails.root + 'tmp' + Rate::CACHING_LOCK_FILE_NAME
160
+ end
161
+ end
@@ -0,0 +1,21 @@
1
+ <h2><%= l(:text_rate_caches_panel) %></h2>
2
+
3
+ <div id="caching-run" class="splitcontentleft">
4
+ <p>
5
+ <%= l(:text_last_caching_run) %><%= h(@last_caching_run) %>
6
+ </p>
7
+
8
+ <p>
9
+ <%= button_to(l(:text_load_missing_caches), {:controller => 'rate_caches', :action => 'update', :cache => 'missing'}, :method => :put) %>
10
+ </p>
11
+ </div>
12
+
13
+ <div id="cache-clearing-run" class="splitcontentright">
14
+ <p>
15
+ <%= l(:text_last_cache_clearing_run) %><%= h(@last_cache_clearing_run) %>
16
+ </p>
17
+
18
+ <p>
19
+ <%= button_to(l(:text_clear_and_load_all_caches), {:controller => 'rate_caches', :action => 'update', :cache => 'reload'}, :method => :put) %>
20
+ </p>
21
+ </div>
@@ -0,0 +1,36 @@
1
+ <% form_for(@rate) do |f| %>
2
+ <table class="list">
3
+ <thead>
4
+ <th style="width:15%"><%= l(:label_date) %></th>
5
+ <th><%= l(:label_project) %></th>
6
+ <th style="width:15%"><%= l(:rate_label_rate) %></th>
7
+ <th style="width:5%"></th>
8
+ </thead>
9
+ <tbody>
10
+ <tr class="odd">
11
+ <td>
12
+ <%= f.text_field "date_in_effect", :size => 10 %><%= calendar_for('rate_date_in_effect') %>
13
+ </td>
14
+ <td>
15
+ <%= # TODO: move to controller once a hook is in place for the Admin panel
16
+ projects = Project.find(:all, :conditions => { :status => Project::STATUS_ACTIVE})
17
+
18
+ select_tag("rate[project_id]", project_options_for_select_with_selected(projects, @rate.project))
19
+ %>
20
+ </td>
21
+ <td align="right">
22
+ <%= l(:rate_label_currency) %> <%= f.text_field "amount", :size => 10 %>
23
+ </td>
24
+ <td align="center">
25
+ <%= f.hidden_field "user_id" %>
26
+ <%= hidden_field_tag "back_url", @back_url %>
27
+ <% if @rate.unlocked? %>
28
+ <%= submit_tag((@rate.new_record? ? l(:button_add) : l(:button_update)), :class => 'button-small')-%>
29
+ <% else %>
30
+ <%= image_tag('locked.png') %>
31
+ <% end %>
32
+ </td>
33
+ </tr>
34
+ </tbody>
35
+ </table>
36
+ <% end %>
@@ -0,0 +1,42 @@
1
+ <table class="list">
2
+ <thead>
3
+ <%= rate_sort_header_tag("date_in_effect",
4
+ :caption => l(:label_date),
5
+ :default_order => 'desc',
6
+ :style => "width: 15%",
7
+ :method => :get,
8
+ :update => "rate_history",
9
+ :user_id => @user.id) %>
10
+ <%= rate_sort_header_tag("project_id",
11
+ :caption => l(:label_project),
12
+ :default_order => 'asc',
13
+ :method => :get,
14
+ :update => "rate_history",
15
+ :user_id => @user.id) %>
16
+ <th style="width:15%"><%= l(:rate_label_rate) %></th>
17
+ <th style="width:5%"></th>
18
+ </thead>
19
+ <tbody>
20
+ <% @rates.each do |rate| %>
21
+ <tr class="<%= cycle 'odd', 'even' %>">
22
+ <td><%= h format_date(rate.date_in_effect) %></td>
23
+ <td>
24
+ <% if rate.project %>
25
+ <%= link_to(h(rate.project), :controller => 'projects', :action => 'show', :id => rate.project) %>
26
+ <% else %>
27
+ <em><%= l(:rate_label_default) %></em>
28
+ <% end %>
29
+ </td>
30
+ <td align="right"><%= h rate.amount %></td>
31
+ <td align="center">
32
+ <% if rate.unlocked? %>
33
+ <%= link_to image_tag('edit.png'), edit_rate_path(rate, :back_url => @back_url) %>
34
+ <%= link_to image_tag('delete.png'), rate_path(rate, :back_url => @back_url), :method => :delete, :confirm => l(:text_are_you_sure) %>
35
+ <% else %>
36
+ <%= image_tag('locked.png') %>
37
+ <% end %>
38
+ </td>
39
+ </tr>
40
+ </tbody>
41
+ <% end; reset_cycle %>
42
+ </table>
@@ -0,0 +1,2 @@
1
+ element = 'rate_' + @rate.project_id.to_s + '_' + @rate.user_id.to_s
2
+ page.replace_html element, "<strong>#{link_to number_to_currency(@rate.amount), { :controller => 'users', :action => 'edit', :id => @rate.user, :tab => 'rates'} }</strong>"
@@ -0,0 +1,5 @@
1
+ content = render_flash_messages
2
+ page.select("div.flash").each do |value, index|
3
+ page.hide value
4
+ end
5
+ page.insert_html :top, 'content', content
@@ -0,0 +1,3 @@
1
+ <h1>Editing rate</h1>
2
+
3
+ <%= render :partial => 'rates/form' %>
@@ -0,0 +1,5 @@
1
+ <div id="rate_history">
2
+ <h1><%= l(:rate_label_rate_history) %></h1>
3
+
4
+ <%= render :partial => 'list' %>
5
+ </div>
@@ -0,0 +1,3 @@
1
+ <h1><%= l(:rate_label_new_rate) %></h1>
2
+
3
+ <%= render :partial => 'rates/form' %>
@@ -0,0 +1,23 @@
1
+ <p>
2
+ <b>Amount:</b>
3
+ <%=h @rate.amount %>
4
+ </p>
5
+
6
+ <p>
7
+ <b>User:</b>
8
+ <%=h @rate.user_id %>
9
+ </p>
10
+
11
+ <p>
12
+ <b>Project:</b>
13
+ <%=h @rate.project_id %>
14
+ </p>
15
+
16
+ <p>
17
+ <b>Date in effect:</b>
18
+ <%=h @rate.date_in_effect %>
19
+ </p>
20
+
21
+
22
+ <%= link_to 'Edit', edit_rate_path(@rate) %> |
23
+ <%= link_to 'Back', rates_path %>
@@ -0,0 +1,23 @@
1
+ <td id="rate_<%= membership.project.id %>_<%= membership.user.id %>">
2
+ <% rate = Rate.for(user, membership.project) %>
3
+
4
+ <% if rate.nil? || rate.default? %>
5
+ <% if rate && rate.default? %>
6
+ <em><%= number_to_currency(rate.amount) %></em>
7
+ <% end %>
8
+
9
+ <% remote_form_for(:rate, :url => rates_path(:format => 'js')) do |f| %>
10
+
11
+ <%= f.text_field :amount %>
12
+ <%= f.hidden_field :date_in_effect, :value => Date.today.to_s, :id => "" %>
13
+ <%= f.hidden_field :project_id, :value => membership.project.id %>
14
+ <%= f.hidden_field :user_id, :value => user.id %>
15
+ <%= hidden_field_tag "back_url", url_for(:controller => 'users', :action => 'edit', :id => user, :tab => 'memberships') %>
16
+
17
+ <%= submit_tag(l(:rate_label_set_rate), :class => "small") %>
18
+ <% end %>
19
+ <% else %>
20
+ <strong><%= link_to number_to_currency(rate.amount), { :action => 'edit', :id => user, :tab => 'rates'} %></strong>
21
+ <% end %>
22
+
23
+ </td>
@@ -0,0 +1,17 @@
1
+ <h1><%= l(:rate_label_new_rate) %></h1>
2
+
3
+ <% @rate = Rate.new(:user => @user ) %>
4
+ <% @back_url = url_for(:controller => 'users', :action => 'edit', :id => @user, :tab => 'rates') %>
5
+ <%= render :partial => 'rates/form' %>
6
+
7
+ <div id="rate_history">
8
+ <h1><%= l(:rate_label_rate_history) %></h1>
9
+ <%# TODO: Refactor out of the view once there is a hook in the controller (Post 0.8.0). %>
10
+ <%# Can't expect everyone to upgrade at the moment %>
11
+ <% sort_init "#{Rate.table_name}.date_in_effect", "desc" %>
12
+ <% sort_update RatesController::ValidSortOptions %>
13
+
14
+ <% @rates = Rate.history_for_user(@user, "#{Rate.table_name}.date_in_effect desc") %>
15
+
16
+ <%= render :partial => 'rates/list' %>
17
+ </div>
Binary file
@@ -0,0 +1,18 @@
1
+ de:
2
+ rate_label_rates: Betraege
3
+ rate_label_rate: Betrag
4
+ rate_label_rate_history: Betragsverlauf
5
+ rate_label_new_rate: Neuer Betrag
6
+ rate_label_currency: EUR
7
+ rate_error_user_not_found: Benutzer nicht gefunden
8
+ rate_label_set_rate: Betrag setzen
9
+ rate_label_default: Standard Betrag
10
+ rate_cost: Kosten
11
+ text_rate_caches_panel: "Betrags Cache"
12
+ text_no_cache_run: "kein Cache gefunden"
13
+ text_last_caching_run: "Zuletzt Cache erstellt: "
14
+ text_last_cache_clearing_run: "Last cache clearing run at: "
15
+ text_load_missing_caches: "Load Missing Caches"
16
+ text_clear_and_load_all_caches: "Clear and Load All Caches"
17
+ text_caches_loaded_successfully: "Caches loaded successfully"
18
+
@@ -0,0 +1,18 @@
1
+ en:
2
+ rate_label_rates: Rates
3
+ rate_label_rate: Rate
4
+ rate_label_rate_history: Rate History
5
+ rate_label_new_rate: New Rate
6
+ rate_label_currency: $
7
+ rate_error_user_not_found: User not found
8
+ rate_label_set_rate: Set Rate
9
+ rate_label_default: Default Rate
10
+ rate_cost: Cost
11
+ text_rate_caches_panel: "Rate Caches"
12
+ text_no_cache_run: "no cache run found"
13
+ text_last_caching_run: "Last caching run at: "
14
+ text_last_cache_clearing_run: "Last cache clearing run at: "
15
+ text_load_missing_caches: "Load Missing Caches"
16
+ text_clear_and_load_all_caches: "Clear and Load All Caches"
17
+ text_caches_loaded_successfully: "Caches loaded successfully"
18
+
@@ -0,0 +1,20 @@
1
+ fr:
2
+ rate_label_rates: Tarifs
3
+ rate_label_rate: Tarif
4
+ rate_label_rate_history: Historique du tarif
5
+ rate_label_new_rate: Nouveau tarif
6
+ rate_label_currency: €
7
+ rate_error_user_not_found: Utilisateur introuvable
8
+ rate_label_set_rate: Parametrage du tarif
9
+ rate_label_default: Tarif par défaut
10
+ rate_error_user_not_found: Utilisateur non trouvé
11
+ rate_label_set_rate: Définir le tarif
12
+ rate_cost: Coût
13
+ text_rate_caches_panel: "Caches des tarifs"
14
+ text_no_cache_run: "pas de cache actif trouvé"
15
+ text_last_caching_run: "Dernier cache actif à : "
16
+ text_last_cache_clearing_run: "Dernier cache purgé à : "
17
+ text_load_missing_caches: "Charger les caches manquants"
18
+ text_clear_and_load_all_caches: "Recharger tous les caches"
19
+ text_caches_loaded_successfully: "Caches chargés avec succés"
20
+
@@ -0,0 +1,9 @@
1
+ ru:
2
+ rate_label_rates: Платежи
3
+ rate_label_rate: Платеж
4
+ rate_label_rate_history: История платежей
5
+ rate_label_new_rate: Новый платежe
6
+ rate_label_currency: $
7
+ rate_error_user_not_found: Пользователь не найден
8
+ rate_label_set_rate: Установить платеж
9
+ rate_label_default: Платеж по умолчанию
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.resources :rates
3
+ map.connect 'rate_caches', :conditions => {:method => :put}, :controller => 'rate_caches', :action => 'update'
4
+ end
data/init.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'redmine'
2
+
3
+ # Patches to the Redmine core
4
+ require 'dispatcher'
5
+
6
+ Dispatcher.to_prepare :redmine_rate do
7
+ gem 'lockfile'
8
+
9
+ require_dependency 'sort_helper'
10
+ SortHelper.send(:include, RateSortHelperPatch)
11
+
12
+ require_dependency 'time_entry'
13
+ TimeEntry.send(:include, RateTimeEntryPatch)
14
+
15
+ require_dependency 'users_helper'
16
+ UsersHelper.send(:include, RateUsersHelperPatch) unless UsersHelper.included_modules.include?(RateUsersHelperPatch)
17
+ end
18
+
19
+ # Hooks
20
+ require 'rate_project_hook'
21
+ require 'rate_memberships_hook'
22
+
23
+ Redmine::Plugin.register :redmine_rate do
24
+ name 'Rate'
25
+ author 'Eric Davis'
26
+ url 'https://projects.littlestreamsoftware.com/projects/redmine-rate'
27
+ author_url 'http://www.littlestreamsoftware.com'
28
+ description "The Rate plugin provides an API that can be used to find the rate for a Member of a Project at a specific date. It also stores historical rate data so calculations will remain correct in the future."
29
+ version '0.2.0'
30
+
31
+ requires_redmine :version_or_higher => '1.0.0'
32
+
33
+ # These settings are set automatically when caching
34
+ settings(:default => {
35
+ 'last_caching_run' => nil
36
+ })
37
+
38
+ permission :view_rate, { }
39
+
40
+ menu :admin_menu, :rate_caches, { :controller => 'rate_caches', :action => 'index'}, :caption => :text_rate_caches_panel
41
+ end
42
+
43
+ require 'redmine_rate/hooks/timesheet_hook_helper'
44
+ require 'redmine_rate/hooks/plugin_timesheet_views_timesheets_time_entry_row_class_hook'
45
+ require 'redmine_rate/hooks/plugin_timesheet_views_timesheet_group_header_hook'
46
+ require 'redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_hook'
47
+ require 'redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_sum_hook'
48
+ require 'redmine_rate/hooks/plugin_timesheet_view_timesheets_report_header_tags_hook'
49
+ require 'redmine_rate/hooks/view_layouts_base_html_head_hook'
data/lang/de.yml ADDED
@@ -0,0 +1,9 @@
1
+ # English strings go here
2
+ rate_label_rates: Betraege
3
+ rate_label_rate: Betrag
4
+ rate_label_rate_history: Betragsverlauf
5
+ rate_label_new_rate: Neuer Betrag
6
+ rate_label_currency: EUR
7
+ rate_error_user_not_found: Benutzer nicht gefunden
8
+ rate_label_set_rate: Betrag setzen
9
+ rate_label_default: Standard Betrag
data/lang/en.yml ADDED
@@ -0,0 +1,9 @@
1
+ # English strings go here
2
+ rate_label_rates: Rates
3
+ rate_label_rate: Rate
4
+ rate_label_rate_history: Rate History
5
+ rate_label_new_rate: New Rate
6
+ rate_label_currency: $
7
+ rate_error_user_not_found: User not found
8
+ rate_label_set_rate: Set Rate
9
+ rate_label_default: Default Rate
data/lang/fr.yml ADDED
@@ -0,0 +1,8 @@
1
+ rate_label_rates: Tarifs
2
+ rate_label_rate: Tarif
3
+ rate_label_rate_history: Historique du tarif
4
+ rate_label_new_rate: Nouveau tarif
5
+ rate_label_currency: €
6
+ rate_error_user_not_found: Utilisateur introuvable
7
+ rate_label_set_rate: Parametrage du tarif
8
+ rate_label_default: Tarif par défaut