Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd7c6892f3 | ||
|
|
193a41aa46 | ||
|
|
9799244a8f | ||
|
|
2a324d6dca | ||
|
|
bc56e88e35 | ||
|
|
bbe55e098a | ||
|
|
dd3bcd9dce | ||
|
|
a9dcecc838 | ||
|
|
f42b419bf3 | ||
|
|
8288455356 | ||
|
|
dc8c5eefd2 | ||
|
|
00453672c6 | ||
|
|
268b1b7107 | ||
|
|
25c3bf5898 | ||
|
|
4c3e6b6b6d | ||
|
|
b20ebe587b |
1
.github_origin
Normal file
1
.github_origin
Normal file
@@ -0,0 +1 @@
|
||||
https://github.com/edavis10/redmine_rate.git
|
||||
4
Rakefile
4
Rakefile
@@ -19,7 +19,6 @@ begin
|
||||
s.homepage = "https://projects.littlestreamsoftware.com/projects/redmine-rate"
|
||||
s.description = "The Rate plugin stores billing rates for Users. It also provides an API that can be used to find the rate for a Member of a Project at a specific date."
|
||||
s.authors = ["Eric Davis"]
|
||||
s.rubyforge_project = "redmine_rate" # TODO
|
||||
s.files = FileList[
|
||||
"[A-Z]*",
|
||||
"init.rb",
|
||||
@@ -29,9 +28,6 @@ begin
|
||||
]
|
||||
end
|
||||
Jeweler::GemcutterTasks.new
|
||||
Jeweler::RubyforgeTasks.new do |rubyforge|
|
||||
rubyforge.doc_task = "rdoc"
|
||||
end
|
||||
rescue LoadError
|
||||
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
||||
end
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
module RateSortHelperPatch
|
||||
def self.included(base) # :nodoc:
|
||||
base.send(:include, InstanceMethods)
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
module RateHelper
|
||||
# Allows more parameters than the standard sort_header_tag
|
||||
def rate_sort_header_tag(column, options = {})
|
||||
caption = options.delete(:caption) || titleize(Inflector::humanize(column))
|
||||
caption = options.delete(:caption) || titleize(ActiveSupport::Inflector::humanize(column))
|
||||
default_order = options.delete(:default_order) || 'asc'
|
||||
options[:title]= l(:label_sort_by, "\"#{caption}\"") unless options[:title]
|
||||
content_tag('th',
|
||||
@@ -32,7 +27,7 @@ module RateSortHelperPatch
|
||||
# Trunk version of sort_link. Was modified in r2571 of Redmine
|
||||
def rate_sort_link_trunk_version(column, caption, default_order, options = { })
|
||||
css, order = nil, default_order
|
||||
|
||||
|
||||
if column.to_s == @sort_criteria.first_key
|
||||
if @sort_criteria.first_asc?
|
||||
css = 'sort asc'
|
||||
@@ -47,7 +42,7 @@ module RateSortHelperPatch
|
||||
sort_options = { :sort => @sort_criteria.add(column.to_s, order).to_param }
|
||||
# don't reuse params if filters are present
|
||||
url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options)
|
||||
|
||||
|
||||
# Add project_id to url_options
|
||||
url_options = url_options.merge(:project_id => params[:project_id]) if params.has_key?(:project_id)
|
||||
|
||||
@@ -57,11 +52,11 @@ module RateSortHelperPatch
|
||||
url_options[:user_id] ||= options[:user_id]
|
||||
#####
|
||||
|
||||
|
||||
link_to_remote(caption,
|
||||
{:update => options[:update] || "content", :url => url_options, :method => options[:method] || :post},
|
||||
link_to(caption,
|
||||
{:remote => true, :update => options[:update] || "content", :url => url_options, :method => options[:method] || :post},
|
||||
{:href => url_for(url_options),
|
||||
:class => css})
|
||||
# link_to caption, :remote => true
|
||||
end
|
||||
|
||||
private
|
||||
@@ -80,12 +75,12 @@ module RateSortHelperPatch
|
||||
icon = nil
|
||||
order = default_order
|
||||
end
|
||||
caption = titleize(Inflector::humanize(column)) unless caption
|
||||
|
||||
caption = titleize(ActiveSupport::Inflector::humanize(column)) unless caption
|
||||
|
||||
sort_options = { :sort_key => column, :sort_order => order }
|
||||
# don't reuse params if filters are present
|
||||
url_options = params.has_key?(:set_filter) ? sort_options : params.merge(sort_options)
|
||||
|
||||
|
||||
##### Hard code url to the Rates index
|
||||
url_options[:controller] = 'rates'
|
||||
url_options[:action] = 'index'
|
||||
@@ -93,10 +88,9 @@ module RateSortHelperPatch
|
||||
#####
|
||||
|
||||
link_to_remote(caption,
|
||||
{:update => options[:update] || "content", :url => url_options, :method => options[:method] || :post},
|
||||
{:remote => true, :update => options[:update] || "content", :url => url_options, :method => options[:method] || :post},
|
||||
{:href => url_for(url_options)}) +
|
||||
(icon ? nbsp(2) + image_tag(icon) : '')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -8,36 +8,36 @@ class Rate < ActiveRecord::Base
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
has_many :time_entries
|
||||
|
||||
|
||||
validates_presence_of :user_id
|
||||
validates_presence_of :date_in_effect
|
||||
validates_numericality_of :amount
|
||||
|
||||
|
||||
before_save :unlocked?
|
||||
after_save :update_time_entry_cost_cache
|
||||
before_destroy :unlocked?
|
||||
after_destroy :update_time_entry_cost_cache
|
||||
|
||||
named_scope :history_for_user, lambda { |user, order|
|
||||
|
||||
scope :history_for_user, lambda { |user, order|
|
||||
{
|
||||
:conditions => { :user_id => user.id },
|
||||
:order => order,
|
||||
:include => :project
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def locked?
|
||||
return self.time_entries.length > 0
|
||||
end
|
||||
|
||||
|
||||
def unlocked?
|
||||
return !self.locked?
|
||||
end
|
||||
|
||||
|
||||
def default?
|
||||
return self.project.nil?
|
||||
end
|
||||
|
||||
|
||||
def specific?
|
||||
return !self.default?
|
||||
end
|
||||
@@ -45,7 +45,7 @@ class Rate < ActiveRecord::Base
|
||||
def update_time_entry_cost_cache
|
||||
TimeEntry.update_cost_cache(user, project)
|
||||
end
|
||||
|
||||
|
||||
# API to find the Rate for a +user+ on a +project+ at a +date+
|
||||
def self.for(user, project = nil, date = Date.today.to_s)
|
||||
# Check input since it's a "public" API
|
||||
@@ -56,13 +56,13 @@ class Rate < ActiveRecord::Base
|
||||
end
|
||||
raise Rate::InvalidParameterException.new("project must be a Project instance") unless project.nil? || project.is_a?(Project)
|
||||
Rate.check_date_string(date)
|
||||
|
||||
|
||||
rate = self.for_user_project_and_date(user, project, date)
|
||||
# Check for a default (non-project) rate
|
||||
rate = self.default_for_user_and_date(user, date) if rate.nil? && project
|
||||
rate
|
||||
end
|
||||
|
||||
|
||||
# API to find the amount for a +user+ on a +project+ at a +date+
|
||||
def self.amount_for(user, project = nil, date = Date.today.to_s)
|
||||
rate = self.for(user, project, date)
|
||||
@@ -96,7 +96,7 @@ class Rate < ActiveRecord::Base
|
||||
end
|
||||
store_cache_timestamp('last_cache_clearing_run', Time.now.utc.to_s)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def self.for_user_project_and_date(user, project, date)
|
||||
if project.nil?
|
||||
@@ -107,7 +107,7 @@ class Rate < ActiveRecord::Base
|
||||
user.id,
|
||||
date
|
||||
])
|
||||
|
||||
|
||||
else
|
||||
return Rate.find(:first,
|
||||
:order => 'date_in_effect DESC',
|
||||
@@ -117,9 +117,9 @@ class Rate < ActiveRecord::Base
|
||||
project.id,
|
||||
date
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def self.default_for_user_and_date(user, date)
|
||||
self.for_user_project_and_date(user, nil, date)
|
||||
end
|
||||
@@ -128,7 +128,7 @@ class Rate < ActiveRecord::Base
|
||||
# a Rate::InvalidParameterException otherwise
|
||||
def self.check_date_string(date)
|
||||
raise Rate::InvalidParameterException.new("date must be a valid Date string (e.g. YYYY-MM-DD)") unless date.is_a?(String)
|
||||
|
||||
|
||||
begin
|
||||
Date.parse(date)
|
||||
rescue ArgumentError
|
||||
@@ -144,13 +144,16 @@ class Rate < ActiveRecord::Base
|
||||
# Wait 1 second after stealing a forced lock
|
||||
options = {:retries => 0, :suspend => 1}
|
||||
options[:max_age] = 1 if force
|
||||
|
||||
|
||||
Lockfile(lock_file, options) do
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
if Rails.env.test?
|
||||
require 'object_daddy'
|
||||
include ObjectDaddy
|
||||
|
||||
public
|
||||
generator_for :date_in_effect => Date.today
|
||||
end
|
||||
|
||||
18
app/views/projects/_settings_members.html.haml
Normal file
18
app/views/projects/_settings_members.html.haml
Normal file
@@ -0,0 +1,18 @@
|
||||
%td{:align => "left", :id => "rate_#{project.id}_#{member.user.id}"}
|
||||
- if rate.nil? || rate.default?
|
||||
- if rate && rate.default?
|
||||
%em #{number_to_currency(rate.amount)}
|
||||
- if User.current.admin?
|
||||
= form_tag form_url, :method => :post, :remote => true do
|
||||
= text_field :rate, :amount, :size => 8
|
||||
= hidden_field(:rate,:date_in_effect, :value => Date.today.to_s)
|
||||
= hidden_field(:rate, :project_id, :value => project.id)
|
||||
= hidden_field(:rate, :user_id, :value => member.user.id)
|
||||
= hidden_field_tag("back_url", url_for(:controller => 'projects', :action => 'settings', :id => project, :tab => 'members'))
|
||||
= submit_tag(l(:rate_label_set_rate), :class => "small")
|
||||
- else
|
||||
%strong
|
||||
- if User.current.admin?
|
||||
= link_to number_to_currency(rate.amount), :controller => 'users', :action => 'edit', :id => member.user, :tab => 'rates'
|
||||
- else
|
||||
= number_to_currency(rate.amount)
|
||||
@@ -1,4 +1,4 @@
|
||||
<% form_for(@rate) do |f| %>
|
||||
<%= form_for(@rate) do |f| %>
|
||||
<table class="list">
|
||||
<thead>
|
||||
<th style="width:15%"><%= l(:label_date) %></th>
|
||||
@@ -14,7 +14,7 @@
|
||||
<td>
|
||||
<%= # TODO: move to controller once a hook is in place for the Admin panel
|
||||
projects = Project.find(:all, :conditions => { :status => Project::STATUS_ACTIVE})
|
||||
|
||||
|
||||
select_tag("rate[project_id]", project_options_for_select_with_selected(projects, @rate.project))
|
||||
%>
|
||||
</td>
|
||||
|
||||
@@ -3,21 +3,17 @@
|
||||
|
||||
<% if rate.nil? || rate.default? %>
|
||||
<% if rate && rate.default? %>
|
||||
<em><%= number_to_currency(rate.amount) %></em>
|
||||
<em><%= number_to_currency(rate.amount) %></em>
|
||||
<% end %>
|
||||
|
||||
<% remote_form_for(:rate, :url => rates_path(:format => 'js')) do |f| %>
|
||||
|
||||
<%= f.text_field :amount %>
|
||||
<%= f.hidden_field :date_in_effect, :value => Date.today.to_s, :id => "" %>
|
||||
<%= f.hidden_field :project_id, :value => membership.project.id %>
|
||||
<%= f.hidden_field :user_id, :value => user.id %>
|
||||
<%= hidden_field_tag "back_url", url_for(:controller => 'users', :action => 'edit', :id => user, :tab => 'memberships') %>
|
||||
|
||||
<%= submit_tag(l(:rate_label_set_rate), :class => "small") %>
|
||||
<%= form_for(:rate, :url => rates_path(:format => 'js'), :remote => true) do |f| %>
|
||||
<%= f.text_field :amount %>
|
||||
<%= f.hidden_field :date_in_effect, :value => Date.today.to_s, :id => "" %>
|
||||
<%= f.hidden_field :project_id, :value => membership.project.id %>
|
||||
<%= f.hidden_field :user_id, :value => user.id %>
|
||||
<%= hidden_field_tag "back_url", url_for(:controller => 'users', :action => 'edit', :id => user, :tab => 'memberships') %>
|
||||
<%= submit_tag(l(:rate_label_set_rate), :class => "small") %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<strong><%= link_to number_to_currency(rate.amount), { :action => 'edit', :id => user, :tab => 'rates'} %></strong>
|
||||
<strong><%= link_to number_to_currency(rate.amount), { :action => 'edit', :id => user, :tab => 'rates'} %></strong>
|
||||
<% end %>
|
||||
|
||||
</td>
|
||||
|
||||
18
config/locales/cs.yml
Normal file
18
config/locales/cs.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
cs:
|
||||
rate_label_rates: Míra
|
||||
rate_label_rate: Míra
|
||||
rate_label_rate_history: Historie míry
|
||||
rate_label_new_rate: Nová míra
|
||||
rate_label_currency: Kč
|
||||
rate_error_user_not_found: Uživatel nebyl nalezen
|
||||
rate_label_set_rate: Nastavit míru
|
||||
rate_label_default: Výchozí míra
|
||||
rate_cost: Cena
|
||||
text_rate_caches_panel: "Míra mezipaměti"
|
||||
text_no_cache_run: "nebyla nalezena žádná běžící mezipaměť"
|
||||
text_last_caching_run: "Poslední spuštěná mezipaměť: "
|
||||
text_last_cache_clearing_run: "Poslední čištění mezipaměti: "
|
||||
text_load_missing_caches: "Nahrát chybějící mezipaměť"
|
||||
text_clear_and_load_all_caches: "Vyčistit a nahrát všechny mezipaměti"
|
||||
text_caches_loaded_successfully: "Mezipaměti byly nahrány"
|
||||
permission_view_rate: "Zobrazit míry"
|
||||
@@ -1,4 +1,4 @@
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.resources :rates
|
||||
map.connect 'rate_caches', :conditions => {:method => :put}, :controller => 'rate_caches', :action => 'update'
|
||||
end
|
||||
resources :rates
|
||||
|
||||
match 'rate_caches', :to => 'rate_caches#index', :via => "get"
|
||||
match 'rate_caches', :to => 'rate_caches#update', :via => "put"
|
||||
|
||||
17
init.rb
17
init.rb
@@ -1,13 +1,12 @@
|
||||
require 'redmine'
|
||||
|
||||
# Patches to the Redmine core
|
||||
require 'dispatcher'
|
||||
|
||||
Dispatcher.to_prepare :redmine_rate do
|
||||
ActionDispatch::Callbacks.to_prepare do
|
||||
gem 'lockfile'
|
||||
|
||||
require_dependency 'sort_helper'
|
||||
SortHelper.send(:include, RateSortHelperPatch)
|
||||
require_dependency 'application_controller'
|
||||
ApplicationController.send(:include, RateHelper)
|
||||
ApplicationController.send(:helper, :rate)
|
||||
|
||||
require_dependency 'time_entry'
|
||||
TimeEntry.send(:include, RateTimeEntryPatch)
|
||||
@@ -17,8 +16,8 @@ Dispatcher.to_prepare :redmine_rate do
|
||||
end
|
||||
|
||||
# Hooks
|
||||
require 'rate_project_hook'
|
||||
require 'rate_memberships_hook'
|
||||
require_dependency 'rate_project_hook'
|
||||
require_dependency 'rate_memberships_hook'
|
||||
|
||||
Redmine::Plugin.register :redmine_rate do
|
||||
name 'Rate'
|
||||
@@ -26,9 +25,9 @@ Redmine::Plugin.register :redmine_rate do
|
||||
url 'https://projects.littlestreamsoftware.com/projects/redmine-rate'
|
||||
author_url 'http://www.littlestreamsoftware.com'
|
||||
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."
|
||||
version '0.2.0'
|
||||
version '0.2.1'
|
||||
|
||||
requires_redmine :version_or_higher => '1.0.0'
|
||||
requires_redmine :version_or_higher => '2.0.0'
|
||||
|
||||
# These settings are set automatically when caching
|
||||
settings(:default => {
|
||||
|
||||
@@ -5,11 +5,11 @@ class RateMembershipsHook < Redmine::Hook::ViewListener
|
||||
|
||||
def view_users_memberships_table_row(context={})
|
||||
return context[:controller].send(:render_to_string, {
|
||||
:partial => 'users/membership_rate',
|
||||
:locals => {
|
||||
:membership => context[:membership],
|
||||
:user => context[:user]
|
||||
}})
|
||||
|
||||
:partial => 'users/membership_rate',
|
||||
:locals => {
|
||||
:membership => context[:membership],
|
||||
:user => context[:user]
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Hooks to attach to the Redmine Projects.
|
||||
class RateProjectHook < Redmine::Hook::ViewListener
|
||||
|
||||
|
||||
def protect_against_forgery?
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
# Renders an additional table header to the membership setting
|
||||
#
|
||||
# Context:
|
||||
# * :project => Current project
|
||||
#
|
||||
def view_projects_settings_members_table_header(context ={ })
|
||||
def view_projects_settings_members_table_header(context={})
|
||||
return '' unless (User.current.allowed_to?(:view_rate, context[:project]) || User.current.admin?)
|
||||
return "<th>#{l(:rate_label_rate)} #{l(:rate_label_currency)}</td>"
|
||||
return content_tag(:th, "#{l(:rate_label_rate)} #{l(:rate_label_currency)}")
|
||||
end
|
||||
|
||||
|
||||
# Renders an AJAX from to update the member's billing rate
|
||||
#
|
||||
# Context:
|
||||
@@ -22,72 +22,29 @@ class RateProjectHook < Redmine::Hook::ViewListener
|
||||
# * :member => Current Member record
|
||||
#
|
||||
# TODO: Move to a view
|
||||
def view_projects_settings_members_table_row(context = { })
|
||||
member = context[:member]
|
||||
project = context[:project]
|
||||
|
||||
return '' unless (User.current.allowed_to?(:view_rate, project) || User.current.admin?)
|
||||
def view_projects_settings_members_table_row(context={})
|
||||
return '' unless (User.current.allowed_to?(:view_rate, context[:project]) || User.current.admin?)
|
||||
|
||||
if Object.const_defined? 'Group' # 0.8.x compatibility
|
||||
# Groups cannot have a rate
|
||||
return content_tag(:td,'') if member.principal.is_a? Group
|
||||
rate = Rate.for(member.principal, project)
|
||||
return content_tag(:td,'') if context[:member].principal.is_a? Group
|
||||
rate = Rate.for(context[:member].principal, context[:project])
|
||||
else
|
||||
rate = Rate.for(member.user, project)
|
||||
rate = Rate.for(context[:member].user, context[:project])
|
||||
end
|
||||
|
||||
content = ''
|
||||
|
||||
if rate.nil? || rate.default?
|
||||
if rate && rate.default?
|
||||
content << "<em>#{number_to_currency(rate.amount)}</em> "
|
||||
end
|
||||
|
||||
if (User.current.admin?)
|
||||
|
||||
url = {
|
||||
:controller => 'rates',
|
||||
:action => 'create',
|
||||
:method => :post,
|
||||
:protocol => Setting.protocol,
|
||||
:host => Setting.host_name
|
||||
return context[:controller].send(:render_to_string, {
|
||||
partial: "projects/settings_members",
|
||||
locals: {
|
||||
form_url: {
|
||||
:controller => 'rates',
|
||||
:action => 'create'
|
||||
},
|
||||
rate: rate,
|
||||
member: context[:member],
|
||||
project: context[:project]
|
||||
}
|
||||
# Build a form_remote_tag by hand since this isn't in the scope of a controller
|
||||
# and url_rewriter doesn't like that fact.
|
||||
form = form_tag(url, :onsubmit => remote_function(:url => url,
|
||||
:host => Setting.host_name,
|
||||
:protocol => Setting.protocol,
|
||||
:form => true,
|
||||
:method => 'post',
|
||||
:return => 'false' )+ '; return false;')
|
||||
|
||||
form << text_field(:rate, :amount)
|
||||
form << hidden_field(:rate,:date_in_effect, :value => Date.today.to_s)
|
||||
form << hidden_field(:rate, :project_id, :value => project.id)
|
||||
form << hidden_field(:rate, :user_id, :value => member.user.id)
|
||||
form << hidden_field_tag("back_url", url_for(:controller => 'projects', :action => 'settings', :id => project, :tab => 'members', :protocol => Setting.protocol, :host => Setting.host_name))
|
||||
|
||||
form << submit_tag(l(:rate_label_set_rate), :class => "small")
|
||||
form << "</form>"
|
||||
|
||||
content << form
|
||||
end
|
||||
else
|
||||
if (User.current.admin?)
|
||||
|
||||
content << content_tag(:strong, link_to(number_to_currency(rate.amount), {
|
||||
:controller => 'users',
|
||||
:action => 'edit',
|
||||
:id => member.user,
|
||||
:tab => 'rates',
|
||||
:protocol => Setting.protocol,
|
||||
:host => Setting.host_name
|
||||
}))
|
||||
else
|
||||
content << content_tag(:strong, number_to_currency(rate.amount))
|
||||
end
|
||||
end
|
||||
return content_tag(:td, content, :align => 'left', :id => "rate_#{project.id}_#{member.user.id}" )
|
||||
})
|
||||
end
|
||||
|
||||
def model_project_copy_before_save(context = {})
|
||||
|
||||
@@ -9,7 +9,7 @@ module RateTimeEntryPatch
|
||||
unloadable # Send unloadable so it will not be unloaded in development
|
||||
belongs_to :rate
|
||||
|
||||
before_save :cost
|
||||
before_save :recalculate_cost
|
||||
|
||||
end
|
||||
|
||||
@@ -18,11 +18,11 @@ module RateTimeEntryPatch
|
||||
module ClassMethods
|
||||
# Updated the cached cost of all TimeEntries for user and project
|
||||
def update_cost_cache(user, project=nil)
|
||||
c = ARCondition.new
|
||||
c << ["#{TimeEntry.table_name}.user_id = ?", user]
|
||||
c << ["#{TimeEntry.table_name}.project_id = ?", project] if project
|
||||
#c = ARCondition.new
|
||||
c = "#{TimeEntry.table_name}.user_id = %d" % [user.id]
|
||||
c << " AND #{TimeEntry.table_name}.project_id = %d" % [project.id] if project
|
||||
|
||||
TimeEntry.all(:conditions => c.conditions).each do |time_entry|
|
||||
TimeEntry.all(:conditions => c ).each do |time_entry|
|
||||
time_entry.save_cached_cost
|
||||
end
|
||||
end
|
||||
@@ -32,7 +32,9 @@ module RateTimeEntryPatch
|
||||
# Returns the current cost of the TimeEntry based on it's rate and hours
|
||||
#
|
||||
# Is a read-through cache method
|
||||
def cost
|
||||
def cost(options={})
|
||||
store_to_db = options[:store] || false
|
||||
|
||||
unless read_attribute(:cost)
|
||||
if self.rate.nil?
|
||||
amount = Rate.amount_for(self.user, self.project, self.spent_on.to_s)
|
||||
@@ -43,8 +45,13 @@ module RateTimeEntryPatch
|
||||
if amount.nil?
|
||||
write_attribute(:cost, 0.0)
|
||||
else
|
||||
# Write the cost to the database for caching
|
||||
update_attribute(:cost, amount.to_f * hours.to_f)
|
||||
if store_to_db
|
||||
# Write the cost to the database for caching
|
||||
update_attribute(:cost, amount.to_f * hours.to_f)
|
||||
else
|
||||
# Cache to object only
|
||||
write_attribute(:cost, amount.to_f * hours.to_f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -59,6 +66,12 @@ module RateTimeEntryPatch
|
||||
clear_cost_cache
|
||||
update_attribute(:cost, cost)
|
||||
end
|
||||
|
||||
def recalculate_cost
|
||||
clear_cost_cache
|
||||
cost(:store => false)
|
||||
true # for callback
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ module RedmineRate
|
||||
module Hooks
|
||||
class ViewLayoutsBaseHtmlHeadHook < Redmine::Hook::ViewListener
|
||||
def view_layouts_base_html_head(context={})
|
||||
return content_tag(:style, "#admin-menu a.rate-caches { background-image: url('#{image_path('database_refresh.png', :plugin => 'redmine_rate')}'); }", :type => 'text/css')
|
||||
content_tag(:style, "#admin-menu a.rate-caches { background-image: url('#{image_path('database_refresh.png')}'); }", :type => 'text/css')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{redmine_rate}
|
||||
s.version = "0.2.0"
|
||||
s.version = "0.2.1"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["Eric Davis"]
|
||||
s.date = %q{2011-03-03}
|
||||
s.date = %q{2011-04-28}
|
||||
s.description = %q{The Rate plugin stores billing rates for Users. It also provides an API that can be used to find the rate for a Member of a Project at a specific date.}
|
||||
s.email = %q{edavis@littlestreamsoftware.com}
|
||||
s.extra_rdoc_files = [
|
||||
@@ -79,7 +79,6 @@ Gem::Specification.new do |s|
|
||||
s.homepage = %q{https://projects.littlestreamsoftware.com/projects/redmine-rate}
|
||||
s.rdoc_options = ["--charset=UTF-8"]
|
||||
s.require_paths = ["lib"]
|
||||
s.rubyforge_project = %q{redmine_rate}
|
||||
s.rubygems_version = %q{1.3.7}
|
||||
s.summary = %q{A Rate plugin for Redmine to store billing rate for user.}
|
||||
s.test_files = [
|
||||
|
||||
@@ -71,6 +71,21 @@ class RateTimeEntryPatchTest < ActiveSupport::TestCase
|
||||
|
||||
assert_equal 2000.0, @time_entry.read_attribute(:cost)
|
||||
end
|
||||
|
||||
should "clear and recalculate the cache when the attribute is already set but stale" do
|
||||
# Set the cost
|
||||
assert @time_entry.save
|
||||
assert_equal 2000.0, @time_entry.read_attribute(:cost)
|
||||
|
||||
@time_entry.reload
|
||||
@time_entry.hours = 20
|
||||
assert @time_entry.save
|
||||
|
||||
assert_equal 4000.0, @time_entry.read_attribute(:cost)
|
||||
assert_equal 4000.0, @time_entry.reload.cost
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user