5 Commits

Author SHA1 Message Date
Bruno Andrade
dd7c6892f3 fixes for rm2.x 2013-08-16 15:32:50 -03:00
Richard Říman
193a41aa46 code cleanup 2012-12-07 02:25:49 +01:00
Richard Říman
9799244a8f some progress in porting for rm2.x 2012-12-07 02:21:19 +01:00
Richard Říman
2a324d6dca Merge https://github.com/daviscabral/redmine_rate into rm2.x 2012-11-23 15:24:05 +01:00
Davis Zanetti Cabral
bc56e88e35 Redmine 2.x support - Initial work 2012-10-23 02:26:09 -02:00
11 changed files with 95 additions and 126 deletions

View File

@@ -27,7 +27,7 @@ module RateHelper
# 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'
@@ -42,7 +42,7 @@ module RateHelper
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)
@@ -52,11 +52,11 @@ module RateHelper
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
@@ -76,11 +76,11 @@ module RateHelper
order = default_order
end
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'
@@ -88,9 +88,9 @@ module RateHelper
#####
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

View File

@@ -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,7 +144,7 @@ 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

View 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)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

10
init.rb
View File

@@ -1,9 +1,7 @@
require 'redmine'
# Patches to the Redmine core
require 'dispatcher'
Dispatcher.to_prepare :redmine_rate do
ActionDispatch::Callbacks.to_prepare do
gem 'lockfile'
require_dependency 'application_controller'
@@ -18,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'
@@ -29,7 +27,7 @@ Redmine::Plugin.register :redmine_rate do
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.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 => {

View File

@@ -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

View File

@@ -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 = {})

View File

@@ -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

View File

@@ -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