redmine_rate 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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