58 Commits

Author SHA1 Message Date
Richard Říman
7104521c50 added missing czech translations 2012-09-12 16:34:50 +02:00
Richard Říman
07e33f9401 added module name czech translation 2012-07-27 14:20:42 +02:00
Richard Říman
0aeafe75ee Merge https://github.com/edavis10/redmine-budget-plugin 2012-03-02 15:50:13 +01:00
Eric Davis
7d57f10038 Fix incorrect setting in the French locale 2012-03-01 14:02:31 -08:00
Eric Davis
296d9f6d75 Merge pull request #1 from voondo/master
French translation
2012-01-19 14:47:00 -08:00
Romain Lalaut
c8266b2b62 french translation 2012-01-16 20:29:44 +01:00
Palo Delincak
bc33dd1127 preklad 2011-12-21 11:41:04 +01:00
Palo Delincak
fba5042ba3 preklad 2011-12-21 10:05:57 +01:00
Palo Delincak
fd4b640c2f preklady budgetu 2011-12-21 09:58:41 +01:00
Richard
952646f485 translates 2011-11-30 09:28:43 +01:00
Eric Davis
6c37e97bee [#3724] Use en-GB.yml for British 2010-11-18 08:47:38 -08:00
Eric Davis
cce10b04d8 Merge remote branch 'pasco/master'
Conflicts:
	app/views/deliverables/_deliverable_summary_row.html.erb
2010-11-18 08:45:59 -08:00
Eric Davis
04d0be203e [#2809] Add some extra css classes on the deliverables list 2010-11-18 08:42:57 -08:00
Eric Davis
1d608a6c47 Fix for setting the deliverable on new issues. 2010-08-18 13:37:01 -07:00
Eric Davis
d3e7f2d2ae Fix the bulk edit style from Redmine Core changes. 2010-07-01 13:37:37 -07:00
Eric Davis
aa9649fde3 [#4225] Add the Deliverable as a criteria in the Time Entries Report. 2010-07-01 13:34:24 -07:00
Eric Davis
e25f841fe2 [#4225] Generated a hook for the timelog available criterias. 2010-07-01 13:13:08 -07:00
Eric Davis
b3c625d9d9 Move init back into init.rb. 2010-07-01 12:57:23 -07:00
Jason Derrett
4b1a4cd156 For client project, remove 'view all issues' from sidebar 2010-04-21 10:32:16 -05:00
Eric Davis
624b80a128 Fixed a SQL error on postgres 2010-03-22 15:13:13 -07:00
Eric Davis
43862fc3b9 Fixes editing an issue's deliverable.
Broken by the Redmine core in r3308.
2010-03-17 18:45:20 -07:00
Eric Davis
94afb7a1ca Prevent a stack error in development 2010-03-15 15:49:20 -07:00
Jon Pascoe
2125241f38 added new localisation variables to other configs to avoid breaking them 2010-03-03 08:39:56 +00:00
root
4b18f20eba updated views and helpers to specify the currency unit when outputting currency values.
A label_currency localisation variable has been added to config/locales/en.yml
2010-03-03 08:14:39 +00:00
Eric Davis
a53ceb2804 [#3294] Expand the new form if the project doesn't have any deliverables yet. 2009-11-11 13:16:00 -08:00
Eric Davis
d552aa7f6f [#3294] Allow the query string to control if the new deliverable form is visible. 2009-11-11 13:11:21 -08:00
Eric Davis
aa541f42bb Added generated gemspec 2009-10-13 21:23:08 -07:00
Eric Davis
19968fa0b8 Converted init.rb for a rails GemPlugin 2009-10-13 21:21:27 -07:00
Eric Davis
c7ac326959 Added version 2009-10-13 21:13:35 -07:00
Eric Davis
46f53a9332 Added git ignores 2009-10-13 21:12:28 -07:00
Eric Davis
6692dffe0f Added jeweler tasks 2009-10-13 21:12:04 -07:00
Adam Soltys
6a9ead4ad0 Set issues' deliverable_id to NULL if their deliverable is deleted 2009-08-31 15:17:43 -07:00
Adam Soltys
642eb1f4ee Allow description text to wrap 2009-08-27 12:08:56 -07:00
Eric Davis
a55227c9bb [#2863] Split the single deliverable template into 3 partials. 2009-08-07 10:42:03 -07:00
Eric Davis
14a6aa2c28 Added a few plugin hooks for Deliverables 2009-08-05 14:57:52 -07:00
Eric Davis
8413b26a15 [#2431] Use RedminePluginSupport for rake tasks. 2009-04-27 15:40:35 -07:00
Eric Davis
f7733011df [#2431] Reformatted the CREDITS file 2009-04-27 15:35:46 -07:00
Eric Davis
a45253329d [#2431] Updated the Readme and install instructions. 2009-04-27 15:34:49 -07:00
Eric Davis
085dc1deb4 Added an explicit requirement on the Rate plugin. 2009-04-27 15:31:29 -07:00
Eric Davis
425047f305 [#2431] Updated the Readme to rdoc 2009-04-27 15:27:36 -07:00
Eric Davis
ffb20347ce Renamed README 2009-04-27 15:21:45 -07:00
Eric Davis
ee2e2dae8e [#2431] Requiring Redmine 0.8.x now 2009-04-27 15:21:12 -07:00
Eric Davis
3b26d08f0d [#2405] Make the Total Budget in the header bold 2009-04-27 15:13:11 -07:00
Eric Davis
833c2877af [#2403] [#2404] Added a Total Budget column to the Deliverable row. 2009-04-27 15:07:30 -07:00
Eric Davis
d88fce97f8 [#1498] Added the Deliverable description to the expanded row. 2009-04-27 14:22:20 -07:00
Eric Davis
931c1ef6ed [#1999] Added a link to expand/collapse all Deliverable rows to the sidebar 2009-04-27 14:13:51 -07:00
Eric Davis
2018561e6a [#1919] Moved the JavaScript for calculating the Budget to an assets file. 2009-04-27 13:57:52 -07:00
Eric Davis
83d5af4a4a [#2157] Added Lithuanian translation by Sergej Jegorov. 2009-04-27 13:44:02 -07:00
Eric Davis
eb60581d1a [#1840] Added Spanish and Catalan translations from Pau Garcia i Quiles. 2009-04-27 13:33:32 -07:00
Eric Davis
e218218ffb [#1840] Completed the i18n string support.
Thanks to Pau Garcia i Quiles for the patch
2009-04-27 13:31:47 -07:00
Eric Davis
7ed5c20a93 [#1959] Fixed a typo in the css class name. 2009-04-27 13:24:44 -07:00
Eric Davis
72f2f77a60 [#2040] Added Hungarian translation by Gergő Jónás. 2009-04-27 13:20:11 -07:00
Eric Davis
fd891e7f51 Changed the Redmine core patches to use the Rails dispatcher.
This will let the core patches work in development mode when the classes
are reloaded.
2009-04-10 08:49:12 -07:00
Eric Davis
71ab0c6b54 [#2301] Check that the session has a sort before trying to access it. 2009-04-01 08:43:37 -07:00
Eric Davis
328cd3fc3f Removed GLoc calls, should stay 0.8 compatibile 2009-02-26 17:09:52 -08:00
Eric Davis
4205fb95a3 Updated to the Rails 2.2.x i18n format 2009-02-26 17:09:12 -08:00
Eric Davis
3f00f12107 Added a hook into Project#copy so Deliverables are copied to the new project. 2009-02-25 10:26:43 -08:00
Eric Davis
3720c486b4 Added a Deliverable#to_s so the Deliverable subject can be printed. 2009-02-18 14:44:06 -08:00
45 changed files with 1262 additions and 414 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
*.tar.gz
*.zip
.DS_Store
coverage
doc
pkg
rdoc

View File

@@ -1,10 +1,6 @@
Thanks go to the following people for patches and contributions: Thanks go to the following people for patches and contributions:
Eric Davis of Little Stream Software * Eric Davis of Little Stream Software - Project Maintainer
- Project Maintainer * Peter Chester of Shane and Peter, Inc - Project sponsorship
* Shane Pearlman of Shane and Peter, Inc - Project sponsorship
Peter Chester of Shane and Peter, Inc
- Project sponsership
Shane Pearlman of Shane and Peter, Inc
- Project sponsership

View File

@@ -1,8 +1,8 @@
## Budget Plugin == Budget Plugin
Budget is a plugin to manage the set of deliverables for each project, automatically calculating key performance indicators. Budget is a plugin to manage the set of deliverables for each project, automatically calculating key performance indicators.
## Features == Features
* Add new deliverable to a project - Fixed bid or Time Based * Add new deliverable to a project - Fixed bid or Time Based
* New issues appended using JavaScript to the top of the deliverables list * New issues appended using JavaScript to the top of the deliverables list
@@ -29,44 +29,46 @@ Budget is a plugin to manage the set of deliverables for each project, automatic
* Ability to add the Deliverable column to the Main Issues list, including sorting and filtering by Deliverable name * Ability to add the Deliverable column to the Main Issues list, including sorting and filtering by Deliverable name
* Adding the billable rate to project members on a per project basis * Adding the billable rate to project members on a per project basis
## Getting the plugin == Getting the plugin
A copy of the plugin can be found in the [downloads](https://projects.littlestreamsoftware.com/projects/list_files/redmine-budget) at Little Stream Software and also on [GitHub](http://github.com/edavis10/redmine-budget-plugin/tree/master). Make sure you install the plugin to @vendor/plugins/budget_plugin@. A copy of the plugin can be found in the {downloads}[https://projects.littlestreamsoftware.com/projects/redmine-budget/files] at Little Stream Software and also on {GitHub}[http://github.com/edavis10/redmine-budget-plugin/tree/master].
## Install == Install
0. Before installing, make sure you are running Redmine after r1796. 1. Follow the Redmine plugin installation steps at: http://www.redmine.org/wiki/redmine/Plugins Make sure the plugin is installed to Make sure you install the plugin to +vendor/plugins/budget_plugin+.
1. Follow the Redmine plugin installation steps at: http://www.redmine.org/wiki/redmine/Plugins 2. The Rate plugin is required also, install it from {Little Stream Software}[https://projects.littlestreamsoftware.com/projects/redmine-rate/files]
2. Login to your Redmine install as an Administrator 3. Run the plugin migrations +rake db:migrate_plugins+
3. Enable the permissions for your Roles 4. Restart your Redmine web servers (e.g. mongrel, thin, mod_rails)
4. Setup your companies defaults in the Plugins' configuration panel 5. Login to your Redmine install as an Administrator
5. Add the "Budget module" to the enabled modules for the projects you want to manage 6. Enable the permissions for your Roles
6. The link to the plugin should appear on that project's navigation 7. Setup your companies defaults in the Plugins' configuration panel
8. Add the "Budget module" to the enabled modules for the projects you want to manage
9. The link to the plugin should appear on that project's navigation
## Usage == Usage
### Adding deliverables === Adding deliverables
1. Use the "New Deliverable" link on sidebar of the Budget page to add a new deliverable 1. Use the "New Deliverable" link on sidebar of the Budget page to add a new deliverable
2. The deliverable can be a Fixed Bid or an Hourly deliverable. The type can be changed later. 2. The deliverable can be a Fixed Bid or an Hourly deliverable. The type can be changed later.
3. Saving the deliverable will add it to the Deliverable list. 3. Saving the deliverable will add it to the Deliverable list.
### Viewing deliverables === Viewing deliverables
1. Clicking the pencil icon will drop down the deliverable details, with summary and calculations about the deliverable. 1. Clicking the pencil icon will drop down the deliverable details, with summary and calculations about the deliverable.
2. Links on the left can be used to edit or delete a deliverable and well as view the issue list for the deliverable. 2. Links on the left can be used to edit or delete a deliverable and well as view the issue list for the deliverable.
### Assignment of an issue to a deliverable === Assignment of an issue to a deliverable
Edit an issue and select the deliverable from the dropdown Edit an issue and select the deliverable from the dropdown
### Bulk assignment of issues to a deliverable === Bulk assignment of issues to a deliverable
1. Select multiple issues on the issue list 1. Select multiple issues on the issue list
2. Right click and select the edit option 2. Right click and select the edit option
3. Select the deliverable from the dropdown and save 3. Select the deliverable from the dropdown and save
### Bulk Assignment of issues by Version === Bulk Assignment of issues by Version
Issues can be bulk assigned to a deliverable based on a Version. Issues can be bulk assigned to a deliverable based on a Version.
@@ -75,16 +77,12 @@ Issues can be bulk assigned to a deliverable based on a Version.
3. Click the Bulk Assign button 3. Click the Bulk Assign button
4. All issues for that version will now be assigned to that Deliverable 4. All issues for that version will now be assigned to that Deliverable
## License == License
This plugin is licensed under the GNU GPL v2. See LICENSE.txt and GPL.txt for details. This plugin is licensed under the GNU GPL v2. See LICENSE.txt and GPL.txt for details.
## Project help == Project help
If you need help you can contact the maintainer at his email address (See CREDITS.txt) or create an issue in the Bug Tracker. If you need help you can contact the maintainer on the bug tracker at https://projects.littlestreamsoftware.com/projects/redmine-budget
### Bug tracker
If you would like to report a bug or request a new feature the bug tracker is located at [https://projects.littlestreamsoftware.com/projects/show/redmine-budget](https://projects.littlestreamsoftware.com/projects/show/redmine-budget)

121
Rakefile
View File

@@ -1,104 +1,35 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require "fileutils" require 'redmine_plugin_support'
require 'rubygems'
gem 'rspec'
gem 'rspec-rails'
Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext } Dir[File.expand_path(File.dirname(__FILE__)) + "/lib/tasks/**/*.rake"].sort.each { |ext| load ext }
# Modifided from the RSpec on Rails plugins RedminePluginSupport::Base.setup do |plugin|
PLUGIN_ROOT = File.expand_path(File.dirname(__FILE__)) plugin.project_name = 'budget_plugin'
plugin.default_task = [:spec]
# Allows loading of an environment config based on the environment
REDMINE_ROOT = ENV["REDMINE_ROOT"] || File.dirname(__FILE__) + "/../../.."
REDMINE_APP = File.expand_path(REDMINE_ROOT + '/app')
REDMINE_LIB = File.expand_path(REDMINE_ROOT + '/lib')
require 'rake'
require 'rake/clean'
require 'rake/rdoctask'
require 'spec/rake/spectask'
PROJECT_NAME = 'budget_plugin'
ZIP_FILE = PROJECT_NAME + ".zip"
CLEAN.include('**/semantic.cache', ZIP_FILE)
# No Database needed
spec_prereq = :noop
task :noop do
end end
task :default => :spec begin
task :stats => "spec:statsetup" require 'jeweler'
Jeweler::Tasks.new do |s|
desc "Run all specs in spec directory (excluding plugin specs)" s.name = "budget_plugin"
Spec::Rake::SpecTask.new(:spec => spec_prereq) do |t| s.summary = "A plugin for Redmine to manage the set of deliverables for each project, automatically calculating key performance indicators."
t.spec_opts = ['--options', "\"#{PLUGIN_ROOT}/spec/spec.opts\""] s.email = "edavis@littlestreamsoftware.com"
t.spec_files = FileList['spec/**/*_spec.rb'] s.homepage = "https://projects.littlestreamsoftware.com/projects/redmine-budget"
end s.description = "A plugin for Redmine to manage the set of deliverables for each project, automatically calculating key performance indicators."
s.authors = ["Eric Davis"]
namespace :spec do s.rubyforge_project = "budget_plugin" # TODO
desc "Run all specs in spec directory with RCov (excluding plugin specs)" s.files = FileList[
Spec::Rake::SpecTask.new(:rcov) do |t| "[A-Z]*",
t.spec_opts = ['--options', "\"#{PLUGIN_ROOT}/spec/spec.opts\""] "init.rb",
t.spec_files = FileList['spec/**/*_spec.rb'] "rails/init.rb",
t.rcov = true "{bin,generators,lib,test,app,assets,config,lang}/**/*",
t.rcov_opts << ["--rails", "--sort=coverage", "--exclude '/var/lib/gems,spec,#{REDMINE_APP},#{REDMINE_LIB}'"] 'lib/jeweler/templates/.gitignore'
]
end end
Jeweler::GemcutterTasks.new
desc "Print Specdoc for all specs (excluding plugin specs)" Jeweler::RubyforgeTasks.new do |rubyforge|
Spec::Rake::SpecTask.new(:doc) do |t| rubyforge.doc_task = "rdoc"
t.spec_opts = ["--format", "specdoc", "--dry-run"]
t.spec_files = FileList['spec/**/*_spec.rb']
end
desc "Print Specdoc for all specs as HTML (excluding plugin specs)"
Spec::Rake::SpecTask.new(:htmldoc) do |t|
t.spec_opts = ["--format", "html:doc/rspec_report.html", "--loadby", "mtime"]
t.spec_files = FileList['spec/**/*_spec.rb']
end
[:models, :controllers, :views, :helpers, :lib].each do |sub|
desc "Run the specs under spec/#{sub}"
Spec::Rake::SpecTask.new(sub => spec_prereq) do |t|
t.spec_opts = ['--options', "\"#{PLUGIN_ROOT}/spec/spec.opts\""]
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
end
end 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 end
desc 'Generate documentation for the plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = PROJECT_NAME
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('*.markdown')
rdoc.rdoc_files.include('*.rdoc')
rdoc.rdoc_files.include('*.txt')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.include('app/**/*.rb')
end
desc 'Uploads project documentation'
task :upload_doc => ['spec:rcov', :doc, 'spec:htmldoc'] do |t|
# TODO: Get rdoc working without frames
`scp -r doc/ dev.littlestreamsoftware.com:/home/websites/projects.littlestreamsoftware.com/shared/embedded_docs/redmine-budget/doc`
`scp -r coverage/ dev.littlestreamsoftware.com:/home/websites/projects.littlestreamsoftware.com/shared/embedded_docs/redmine-budget/coverage`
end
desc "Create release archives"
task :release => [:clean, :rdoc, 'release:zip', 'release:tarball']
namespace :release do
desc "Create a zip archive"
task :zip => [:clean] do
sh "git archive --format=zip --prefix=#{PLUGIN_NAME}/ HEAD > #{PLUGIN_NAME}.zip"
end
desc "Create a tarball archive"
task :tarball => [:clean] do
sh "git archive --format=tar --prefix=#{PLUGIN_NAME}/ HEAD | gzip > #{PLUGIN_NAME}.tar.gz"
end
end

1
VERSION Normal file
View File

@@ -0,0 +1 @@
0.2.0

View File

@@ -13,8 +13,8 @@ class DeliverablesController < ApplicationController
@deliverable_count = Deliverable.count(:conditions => { :project_id => @project.id}) @deliverable_count = Deliverable.count(:conditions => { :project_id => @project.id})
@deliverable_pages = Paginator.new self, @deliverable_count, per_page_option, params['page'] @deliverable_pages = Paginator.new self, @deliverable_count, per_page_option, params['page']
@deliverables = Deliverable.find(:all, @deliverables = Deliverable.find(:all,
{ {
:conditions => { :project_id => @project.id}, :conditions => { :project_id => @project.id},
:limit => per_page_option, :limit => per_page_option,
:offset => @deliverable_pages.current.offset :offset => @deliverable_pages.current.offset
@@ -22,22 +22,25 @@ class DeliverablesController < ApplicationController
) )
@deliverables = sort_if_needed @deliverables @deliverables = sort_if_needed @deliverables
@deliverable = Deliverable.new @deliverable = Deliverable.new
@budget = Budget.new(@project.id) @budget = Budget.new(@project.id)
@display_form = params[:new].present? || @deliverables.empty?
respond_to do |format| respond_to do |format|
format.html { render :action => 'index', :layout => !request.xhr? } format.html { render :action => 'index', :layout => !request.xhr? }
end end
end end
# Action to preview the Deliverable description # Action to preview the Deliverable description
def preview def preview
@text = params[:deliverable][:description] @text = params[:deliverable][:description]
render :partial => 'common/preview' render :partial => 'common/preview'
end end
# Saves a new Deliverable # Saves a new Deliverable
def create def create
if params[:deliverable][:type] == FixedDeliverable.name if params[:deliverable][:type] == FixedDeliverable.name
@@ -47,7 +50,7 @@ class DeliverablesController < ApplicationController
else else
@deliverable = Deliverable.new(params[:deliverable]) @deliverable = Deliverable.new(params[:deliverable])
end end
@deliverable.project = @project @deliverable.project = @project
@budget = Budget.new(@project.id) @budget = Budget.new(@project.id)
respond_to do |format| respond_to do |format|
@@ -70,11 +73,11 @@ class DeliverablesController < ApplicationController
# Updates an existing Deliverable, optionally changing it's type # Updates an existing Deliverable, optionally changing it's type
def update def update
@deliverable = Deliverable.find(params[:deliverable_id]) @deliverable = Deliverable.find(params[:deliverable_id])
if params[:deliverable][:type] != @deliverable.class if params[:deliverable][:type] != @deliverable.class
@deliverable = @deliverable.change_type(params[:deliverable][:type]) @deliverable = @deliverable.change_type(params[:deliverable][:type])
end end
respond_to do |format| respond_to do |format|
if @deliverable.update_attributes(params[:deliverable]) if @deliverable.update_attributes(params[:deliverable])
@flash = l(:notice_successful_create) @flash = l(:notice_successful_create)
@@ -84,20 +87,20 @@ class DeliverablesController < ApplicationController
end end
end end
end end
# Removes the Deliverable # Removes the Deliverable
def destroy def destroy
@deliverable = Deliverable.find_by_id_and_project_id(params[:deliverable_id], @project.id) @deliverable = Deliverable.find_by_id_and_project_id(params[:deliverable_id], @project.id)
render_404 and return unless @deliverable render_404 and return unless @deliverable
render_403 and return unless @deliverable.editable_by?(User.current) render_403 and return unless @deliverable.editable_by?(User.current)
@deliverable.destroy @deliverable.destroy
flash[:notice] = l(:notice_successful_delete) flash[:notice] = l(:notice_successful_delete)
redirect_to :action => 'index', :id => @project.id redirect_to :action => 'index', :id => @project.id
end end
# Create a query in the session and redirects to the issue list with that query # Create a query in the session and redirects to the issue list with that query
def issues def issues
@query = Query.new(:name => "_") @query = Query.new(:name => "_")
@@ -113,41 +116,41 @@ class DeliverablesController < ApplicationController
redirect_to :controller => 'issues', :action => 'index', :project_id => @project.id redirect_to :controller => 'issues', :action => 'index', :project_id => @project.id
end end
# Assigns issues to the Deliverable based on their Version # Assigns issues to the Deliverable based on their Version
def bulk_assign_issues def bulk_assign_issues
@deliverable = Deliverable.find_by_id_and_project_id(params[:deliverable_id], @project.id) @deliverable = Deliverable.find_by_id_and_project_id(params[:deliverable_id], @project.id)
render_404 and return unless @deliverable render_404 and return unless @deliverable
render_403 and return unless @deliverable.editable_by?(User.current) render_403 and return unless @deliverable.editable_by?(User.current)
number_updated = @deliverable.assign_issues_by_version(params[:version][:id]) number_updated = @deliverable.assign_issues_by_version(params[:version][:id])
flash[:notice] = l(:message_updated_issues, number_updated) flash[:notice] = l(:message_updated_issues, number_updated)
redirect_to :action => 'index', :id => @project.id redirect_to :action => 'index', :id => @project.id
end end
private private
def find_project def find_project
@project = Project.find(params[:id]) @project = Project.find(params[:id])
end end
def get_settings def get_settings
@settings = Setting.plugin_budget_plugin @settings = Setting.plugin_budget_plugin
end end
# Sorting orders # Sorting orders
def sort_order def sort_order
if %w(score spent progress labor_budget).include?(session[@sort_name][:key]) if session[@sort_name] && %w(score spent progress labor_budget).include?(session[@sort_name][:key])
return { } return { }
else else
return { :order => sort_clause } return { :order => sort_clause }
end end
end end
# Sort +deliverables+ manually using the virtual fields # Sort +deliverables+ manually using the virtual fields
def sort_if_needed(deliverables) def sort_if_needed(deliverables)
if %w(score spent progress labor_budget).include?(session[@sort_name][:key]) if session[@sort_name] && %w(score spent progress labor_budget).include?(session[@sort_name][:key])
case session[@sort_name][:key] case session[@sort_name][:key]
when "score": when "score":
sorted = deliverables.sort {|a,b| a.score <=> b.score} sorted = deliverables.sort {|a,b| a.score <=> b.score}
@@ -165,5 +168,4 @@ class DeliverablesController < ApplicationController
return deliverables return deliverables
end end
end end
end end

View File

@@ -73,8 +73,8 @@ module DeliverablesHelper
end end
def toggle_arrows(deliverable_id) def toggle_arrows(deliverable_id)
open_js = "$('deliverable-details-#{deliverable_id}').show(); $$('.toggle_#{deliverable_id}').each(function(e) {e.toggle();})" open_js = "expandRow(#{deliverable_id})"
close_js = "$('deliverable-details-#{deliverable_id}').hide(); $$('.toggle_#{deliverable_id}').each(function(e) {e.toggle();})" close_js = "collapseRow(#{deliverable_id})"
return toggle_arrow(deliverable_id, "toggle-arrow-closed.gif", open_js, false) + return toggle_arrow(deliverable_id, "toggle-arrow-closed.gif", open_js, false) +
toggle_arrow(deliverable_id, "toggle-arrow-open.gif", close_js, true) toggle_arrow(deliverable_id, "toggle-arrow-open.gif", close_js, true)
@@ -86,15 +86,15 @@ module DeliverablesHelper
content_tag(:span, content_tag(:span,
link_to_function(image_tag(image, :plugin => "budget_plugin"), js), link_to_function(image_tag(image, :plugin => "budget_plugin"), js),
:class => "toggle_" + deliverable_id.to_s, :class => "toggle toggle_" + deliverable_id.to_s,
:style => style :style => style
) )
end end
def number_or_percent(number_field, percent_field) def number_or_percent(number_field, percent_field)
return number_to_currency(number_field, :precision => 0) unless number_field.blank? return number_to_currency(number_field, :unit => l(:label_currency), :precision => 0) unless number_field.blank?
return number_to_percentage(percent_field, :precision => 0) unless percent_field.blank? return number_to_percentage(percent_field, :precision => 0) unless percent_field.blank?
return "$0" return number_to_currency(0, :unit => l(:label_currency), :precision => 0)
end end
end end

View File

@@ -5,7 +5,7 @@ class Deliverable < ActiveRecord::Base
validates_presence_of :subject validates_presence_of :subject
belongs_to :project belongs_to :project
has_many :issues has_many :issues, :dependent => :nullify
# Assign all the issues with +version_id+ to this Deliverable # Assign all the issues with +version_id+ to this Deliverable
def assign_issues_by_version(version_id) def assign_issues_by_version(version_id)
@@ -195,6 +195,11 @@ class Deliverable < ActiveRecord::Base
def hourly? def hourly?
return self.class == HourlyDeliverable return self.class == HourlyDeliverable
end end
def to_s
self.subject
end
private private
def use_issue_status_for_done_ratios? def use_issue_status_for_done_ratios?

View File

@@ -1,13 +1,13 @@
<h2 class="title">Budget</h2> <h2 class="title"><%= l(:budget_title) %></h2>
<table> <table>
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr class="total">
<td> <td>
Total Budget: <%= l(:field_budget) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.budget, :precision => 0) %> <%= h number_to_currency(budget.budget, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
@@ -15,10 +15,10 @@
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr>
<td> <td>
Labor Budget: <%= l(:label_labor_budget) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.labor_budget, :precision => 0) %> <%= h number_to_currency(budget.labor_budget, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
@@ -26,10 +26,10 @@
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr>
<td> <td>
Labor Budget Spent: <%= l(:label_labor_budget_spent) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.spent, :precision => 0) %> <%= h number_to_currency(budget.spent, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
@@ -37,17 +37,17 @@
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr>
<td> <td>
Labor Budget Remaining: <%= l(:label_labor_budget_remaining) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.labor_budget_left, :precision => 0) %> <%= h number_to_currency(budget.labor_budget_left, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
<tr> <tr>
<td> <td>
Progress: <%= l(:label_progress) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_percentage(budget.progress, :precision => 0) %> <%= h number_to_percentage(budget.progress, :precision => 0) %>
@@ -57,7 +57,7 @@
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr>
<td> <td>
Budget Score: <%= l(:label_budget_score) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h budget.score %> <%= h budget.score %>
@@ -68,10 +68,10 @@
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr>
<td> <td>
Overruns: <%= l(:label_overruns) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.overruns, :precision => 0) %> <%= h number_to_currency(budget.overruns, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
@@ -79,10 +79,10 @@
<% if allowed_management? && budget.amount_missing_on_deliverables > 0%> <% if allowed_management? && budget.amount_missing_on_deliverables > 0%>
<tr> <tr>
<td> <td>
Missing on <%= link_to('Deliverables:', :action => 'issues', :id => @project.id, :deliverable_id => :none) %> <%= l(:label_missing_on) %> <%= link_to('Deliverables:', :action => 'issues', :id => @project.id, :deliverable_id => :none) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.amount_missing_on_deliverables, :precision => 0) %> <%= h number_to_currency(budget.amount_missing_on_deliverables, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
@@ -90,7 +90,7 @@
<% if allowed_management? && budget.amount_missing_on_issues > 0 %> <% if allowed_management? && budget.amount_missing_on_issues > 0 %>
<tr> <tr>
<td> <td>
Missing on <%= link_to('Issues:', <%= l(:label_missing_on) %> <%= link_to('Issues:',
:controller => 'timelog', :controller => 'timelog',
:action => 'details', :action => 'details',
:project_id => @project.id, :project_id => @project.id,
@@ -99,14 +99,14 @@
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.amount_missing_on_issues, :precision => 0) %> <%= h number_to_currency(budget.amount_missing_on_issues, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>
<tr> <tr>
<td> <td>
Next Due Date: <%= l(:label_next_due_date) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h distance_of_time_in_words_to_now(budget.next_due_date) if budget.next_due_date %> <%= h distance_of_time_in_words_to_now(budget.next_due_date) if budget.next_due_date %>
@@ -114,7 +114,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
Completion: <%= l(:label_completion) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h distance_of_time_in_words_to_now(budget.final_due_date) if budget.final_due_date %> <%= h distance_of_time_in_words_to_now(budget.final_due_date) if budget.final_due_date %>
@@ -124,10 +124,10 @@
<% if allowed_management? %> <% if allowed_management? %>
<tr> <tr>
<td> <td>
Potential Profit: <%= l(:label_potential_profit) %>
</td> </td>
<td class="calculation-column"> <td class="calculation-column">
<%= h number_to_currency(budget.profit, :precision => 0) %> <%= h number_to_currency(budget.profit, :unit => l(:label_currency), :precision => 0) %>
</td> </td>
</tr> </tr>
<% end %> <% end %>

View File

@@ -1,78 +1,4 @@
<% css = cycle('odd','even') %> <% css = cycle('odd','even') %>
<tr id="deliverable-<%= deliverable.id %>" class="deliverable <%= css %>"> <%= render :partial => 'deliverable_summary_row', :locals => {:deliverable => deliverable, :css => css} %>
<td class="actions"> <%= render :partial => 'deliverable_details_row', :locals => {:deliverable => deliverable, :css => css} %>
<%= toggle_arrows(deliverable.id) %> <%= render :partial => 'deliverable_description_row', :locals => {:deliverable => deliverable, :css => css} %>
</td>
<%= content_tag(:td, deliverable.id) %>
<%= content_tag(:td, h(deliverable.score.to_i), :class => 'score') if allowed_management? %>
<%= content_tag(:td, h(deliverable.subject), :class => 'subject') %>
<%= content_tag(:td, number_to_currency(deliverable.labor_budget || 0.0, :precision => 0), :class => 'budget') if allowed_management? %>
<%= content_tag(:td, number_to_currency(deliverable.spent, :precision => 0), :class => 'spent') if allowed_management? %>
<%= content_tag(:td, format_date(deliverable.due), :class => 'due_date') %>
<%= content_tag(:td, progress_bar(deliverable.progress, :width => '100%', :class => 'dont_ratio')) %>
</tr>
<tr id="deliverable-details-<%= deliverable.id %>" class="deliverable deliverable-details <%= css %>" style="display:none;">
<td class="deliverable-actions">
<%= content_tag(:p,link_to("Edit", :action => 'edit', :id => @project.id, :deliverable_id => deliverable.id)) if allowed_management? -%>
<%= content_tag(:p,link_to("Delete", { :action => 'destroy', :id => @project.id, :deliverable_id => deliverable.id}, :confirm => l(:text_are_you_sure))) if allowed_management? %>
<%= content_tag(:p,link_to("Issues", :action => 'issues', :id => @project.id, :deliverable_id => deliverable.id)) -%>
<% if allowed_management? && @project.versions.size > 0 %>
<div>
<% form_for :deliverable, deliverable, :url => { :action => "bulk_assign_issues", :id => @project.id, :deliverable_id => deliverable.id} do |f| %>
<%= select("version", "id", @project.versions.sort.collect {|v| [v.name, v.id ] }, { :prompt => '-- Version --' }) %><br />
<%= submit_tag("Bulk Assign") %>
<% end %>
</div>
<% end %>
</td>
<td colspan="2">
<table class="progress-table">
<%= row_with_double_data "Progress: ", number_to_percentage(deliverable.progress, :precision => 0), '' %>
<% if allowed_management? %>
<% if deliverable.hourly? %>
<%= row_with_double_data "Hours Estimated: ", number_with_precision(deliverable.total_hours, 0), '' %>
<% end %>
<% if deliverable.fixed? %>
<%= row_with_double_data "Fixed Amount: ", '', number_to_currency(deliverable.fixed_cost, :precision => 0) %>
<% end %>
<% if deliverable.hours_used > 0 %>
<%= row_with_double_data "Hours Used: ", number_with_precision(deliverable.hours_used,0), number_to_currency(deliverable.spent_by_members, :precision => 0) %>
<tr><td>&nbsp;</td></tr>
<% deliverable.members_spent.each do |person| %>
<%= row_with_double_data(h(person.user.name), person.hours.round, number_to_currency(person.spent, :precision => 0)) %>
<% end %>
<% end %>
<% end %>
</table>
</td>
<td colspan="3">
<table class="financials">
<%= row_with_data("Total Budget: ", number_to_currency(deliverable.budget, :precision => 0)) -%>
<% if allowed_management? %>
<% base_price_label = deliverable.hourly? ? "Labor: " : "Fixed Amount: " %>
<%= row_with_data(base_price_label, number_to_currency(deliverable.labor_budget || 0.0, :precision => 0)) -%>
<%= row_with_data("Overhead: ", number_or_percent(deliverable.overhead, deliverable.overhead_percent)) -%>
<%= row_with_data("Materials: ", number_or_percent(deliverable.materials, deliverable.materials_percent)) -%>
<%= row_with_data("Potential Profit: ", number_or_percent(deliverable.profit, deliverable.profit_percent)) -%>
<% end %>
</table>
</td>
<td colspan="2">
<table class="issue-totals">
<%= row_with_data(l(:label_issue_plural), deliverable.issues.size, 'issue-totals') -%>
<% deliverable.issues_with_trackers.each do |tracker_name, count| %>
<%= row_with_data(h(tracker_name), count,'issue-totals') -%>
<% end %>
</table>
</td>
</tr>

View File

@@ -0,0 +1,6 @@
<tr id="deliverable-description-<%= deliverable.id %>" class="deliverable deliverable-details <%= css %>" style="display:none;">
<td colspan="9" style="text-align: left;">
<%= l(:field_description) %>: <%= textilizable deliverable.description %>
</td>
<%= Redmine::Hook.call_hook(:plugin_budget_view_deliverable_description_row, { :deliverable => deliverable }) %>
</tr>

View File

@@ -0,0 +1,65 @@
<tr id="deliverable-details-<%= deliverable.id %>" class="deliverable deliverable-details <%= css %>" style="display:none;">
<td class="deliverable-actions">
<%= content_tag(:p,link_to(l(:button_edit), :action => 'edit', :id => @project.id, :deliverable_id => deliverable.id)) if allowed_management? -%>
<%= content_tag(:p,link_to(l(:button_delete), { :action => 'destroy', :id => @project.id, :deliverable_id => deliverable.id}, :confirm => l(:text_are_you_sure))) if allowed_management? %>
<%= content_tag(:p,link_to(l(:label_issue_plural), :action => 'issues', :id => @project.id, :deliverable_id => deliverable.id)) -%>
<% if allowed_management? && @project.versions.size > 0 %>
<div>
<% form_for :deliverable, deliverable, :url => { :action => "bulk_assign_issues", :id => @project.id, :deliverable_id => deliverable.id} do |f| %>
<%= select("version", "id", @project.versions.sort.collect {|v| [v.name, v.id ] }, { :prompt => '-- Version --' }) %><br />
<%= submit_tag(l(:label_bulk_assign)) %>
<% end %>
</div>
<% end %>
</td>
<td colspan="2">
<table class="progress-table">
<%= row_with_double_data l(:label_progress), number_to_percentage(deliverable.progress, :precision => 0), '' %>
<% if allowed_management? %>
<% if deliverable.hourly? %>
<%= row_with_double_data l(:label_hours_estimated), number_with_precision(deliverable.total_hours, 0), '' %>
<% end %>
<% if deliverable.fixed? %>
<%= row_with_double_data l(:label_fixed_amount), '', number_to_currency(deliverable.fixed_cost, :unit => l(:label_currency), :precision => 0) %>
<% end %>
<% if deliverable.hours_used > 0 %>
<%= row_with_double_data l(:label_hours_used), number_with_precision(deliverable.hours_used,0), number_to_currency(deliverable.spent_by_members, :unit => l(:label_currency), :precision => 0) %>
<tr><td>&nbsp;</td></tr>
<% deliverable.members_spent.each do |person| %>
<%= row_with_double_data(h(person.user.name), person.hours.round, number_to_currency(person.spent, :unit => l(:label_currency), :precision => 0)) %>
<% end %>
<% end %>
<% end %>
</table>
</td>
<td colspan="3">
<table class="financials">
<%= row_with_data(l(:field_budget), number_to_currency(deliverable.budget, :unit => l(:label_currency), :precision => 0)) -%>
<% if allowed_management? %>
<% base_price_label = deliverable.hourly? ? l(:label_labor) : l(:label_fixed_amount) %>
<%= row_with_data(base_price_label, number_to_currency(deliverable.labor_budget || 0.0, :unit => l(:label_currency), :precision => 0)) -%>
<%= row_with_data(l(:label_overhead), number_or_percent(deliverable.overhead, deliverable.overhead_percent)) -%>
<%= row_with_data(l(:label_materials), number_or_percent(deliverable.materials, deliverable.materials_percent)) -%>
<%= row_with_data(l(:label_potential_profit), number_or_percent(deliverable.profit, deliverable.profit_percent)) -%>
<% end %>
</table>
</td>
<td colspan="3">
<table class="issue-totals">
<%= row_with_data(l(:label_issue_plural), deliverable.issues.size, 'issue-totals') -%>
<% deliverable.issues_with_trackers.each do |tracker_name, count| %>
<%= row_with_data(h(tracker_name), count,'issue-totals') -%>
<% end %>
</table>
</td>
<%= Redmine::Hook.call_hook(:plugin_budget_view_deliverable_details_row, { :deliverable => deliverable }) %>
</tr>

View File

@@ -0,0 +1,15 @@
<tr id="deliverable-<%= deliverable.id %>" class="deliverable <%= css %>">
<td class="actions">
<%= toggle_arrows(deliverable.id) %>
</td>
<%= content_tag(:td, deliverable.id, :class => 'id') %>
<%= content_tag(:td, h(deliverable.score.to_i), :class => 'score') if allowed_management? %>
<%= content_tag(:td, h(deliverable.subject), :class => 'subject') %>
<%= content_tag(:td, number_to_currency(deliverable.budget || 0.0, :unit => l(:label_currency), :precision => 0), :class => 'budget') if allowed_management? %>
<%= content_tag(:td, number_to_currency(deliverable.labor_budget || 0.0, :unit => l(:label_currency), :precision => 0), :class => 'budget labor') if allowed_management? %>
<%= content_tag(:td, number_to_currency(deliverable.spent, :unit => l(:label_currency), :precision => 0), :class => 'spent') if allowed_management? %>
<%= content_tag(:td, format_date(deliverable.due), :class => 'due_date') %>
<%= content_tag(:td, progress_bar(deliverable.progress, :width => '100%', :class => 'done_ratio')) %>
<%= Redmine::Hook.call_hook(:plugin_budget_view_deliverable_summary_row, { :deliverable => deliverable }) %>
</tr>

View File

@@ -19,7 +19,7 @@
<tr> <tr>
<td> <td>
<label for="deliverable_type">Fixed Cost</label> <label for="deliverable_type"><%= l(:label_fixed_cost) %></label>
</td> </td>
<td> <td>
<%= check_box(:deliverable, :type, {}, FixedDeliverable.name, HourlyDeliverable.name) %> <%= check_box(:deliverable, :type, {}, FixedDeliverable.name, HourlyDeliverable.name) %>
@@ -31,7 +31,7 @@
<tr class="budget-hourly"> <tr class="budget-hourly">
<td> <td>
<label for="deliverable_cost_per_hour">Cost per hour</label> <label for="deliverable_cost_per_hour"><%= l(:field_cost_per_hour)%></label>
</td> </td>
<td> <td>
<%= text_field :deliverable, :cost_per_hour, :size => 7 %> <%= text_field :deliverable, :cost_per_hour, :size => 7 %>
@@ -42,7 +42,7 @@
<tr class="budget-hourly"> <tr class="budget-hourly">
<td> <td>
<label for="deliverable_total_hours">Total Hours</label> <label for="deliverable_total_hours"><%= l(:field_total_hours) %></label>
</td> </td>
<td> <td>
<%= text_field :deliverable, :total_hours, :size => 7 %> <%= text_field :deliverable, :total_hours, :size => 7 %>
@@ -54,7 +54,7 @@
<tr class="budget-fixed" style="display:none;"> <tr class="budget-fixed" style="display:none;">
<td> <td>
<label for="deliverable_fixed_cost">Fixed Bid</label> <label for="deliverable_fixed_cost"><%= l(:field_fixed_cost) %></label>
</td> </td>
<td> <td>
<%= text_field :deliverable, :fixed_cost, :size => 7 %> <%= text_field :deliverable, :fixed_cost, :size => 7 %>
@@ -69,7 +69,7 @@
<%= field_with_budget_observer_and_totals(f, @deliverable, :profit, :profit_percent, @settings[:budget_profit]) %> <%= field_with_budget_observer_and_totals(f, @deliverable, :profit, :profit_percent, @settings[:budget_profit]) %>
<tr class="total-budget"> <tr class="total-budget">
<td><label>Total Budget:</label></td> <td><label><%= l(:field_budget) %></label></td>
<td></td> <td></td>
<td class="calculation-column"> <td class="calculation-column">
<%= content_tag(:span, 0, :id => 'total-budget-calculation', :class => "budget-calculation") %> <%= content_tag(:span, 0, :id => 'total-budget-calculation', :class => "budget-calculation") %>
@@ -109,120 +109,3 @@
<div id="preview" class="wiki"></div> <div id="preview" class="wiki"></div>
</fieldset> </fieldset>
<% content_for :header_tags do %>
<script type="text/javascript">
var BudgetModule = Class.create();
Object.extend(BudgetModule.prototype, {
initialize: function () {},
toAmount: function(value) {
var amount = value.replace(/[^1234567890.]/ig,'');
if (amount) {
return parseFloat(amount);
} else {
return 0;
}
},
updateAmounts: function() {
if ($('deliverable_type').checked) {
// Fixed cost
var cost = Budget.toAmount($('deliverable_fixed_cost').value);
Budget.updateAmount($('fixedCost'), cost);
} else {
// Variable cost
var perHour = Budget.toAmount($('deliverable_cost_per_hour').value);
var hours = Budget.toAmount($('deliverable_total_hours').value);
var cost = perHour * hours;
Budget.updateAmount($('variableCost'), cost);
}
if ($('deliverable_overhead').value.match('%')) {
var overhead_subtotal = (Budget.toAmount($('deliverable_overhead').value) / 100) * cost;
} else {
var overhead_subtotal = Budget.toAmount($('deliverable_overhead').value);
}
if ($('deliverable_materials').value.match('%')) {
var materials_subtotal = (Budget.toAmount($('deliverable_materials').value) / 100) * cost;
} else {
var materials_subtotal = Budget.toAmount($('deliverable_materials').value);
}
// Profit uses labor cost and overhead
if ($('deliverable_profit').value.match('%')) {
var profit_subtotal = (Budget.toAmount($('deliverable_profit').value) / 100) * (cost + overhead_subtotal);
} else {
var profit_subtotal = Budget.toAmount($('deliverable_profit').value);
}
// Amounts
Budget.updateAmount($('overhead_subtotal'), overhead_subtotal);
Budget.updateAmount($('materials_subtotal'), materials_subtotal);
Budget.updateAmount($('profit_subtotal'), profit_subtotal);
var total = cost + overhead_subtotal + materials_subtotal + profit_subtotal;
$('deliverable_budget').value = total;
$('total-budget-calculation').innerHTML = Budget.number_to_currency(total);
},
updateAmount: function(element, value) {
if (element) {
element.innerHTML = Budget.number_to_currency(value);
}
},
changeType: function() {
if ($('deliverable_type').checked) {
// Fixed
$$('.budget-hourly').each(function(ele) { ele.hide(); });
$$('.budget-fixed').each(function(ele) { ele.show(); });
} else {
// Variable
$$('.budget-hourly').each(function(ele) { ele.show(); });
$$('.budget-fixed').each(function(ele) { ele.hide(); });
}
Budget.updateAmounts();
},
// Rails-like number_to_currency currency formatting
// http://snippets.dzone.com/posts/show/4646
number_to_currency: function (number, options) {
try {
var options = options || {};
var precision = options["precision"] || 2;
var unit = options["unit"] || "$";
var separator = precision > 0 ? options["separator"] || "." : "";
var delimiter = options["delimiter"] || ",";
var parts = parseFloat(number).toFixed(precision).split('.');
return unit + Budget.number_with_delimiter(parts[0], delimiter) + separator + parts[1].toString();
} catch(e) {
return number
}
},
number_with_delimiter: function (number, delimiter, separator) {
try {
var delimiter = delimiter || ",";
var separator = separator || ".";
var parts = number.toString().split('.');
parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter);
return parts.join(separator);
} catch(e) {
return number
}
}
});
Budget = new BudgetModule();
</script>
<% end %>

View File

@@ -2,12 +2,14 @@
<thead><tr> <thead><tr>
<%= content_tag('th', " ") %> <%= content_tag('th', " ") %>
<%= sort_header_tag("#{Deliverable.table_name}.id", :caption => '#', :default_order => 'desc') %> <%= sort_header_tag("#{Deliverable.table_name}.id", :caption => '#', :default_order => 'desc') %>
<%= sort_header_tag("score", :caption => 'Score', :default_order => 'desc') if allowed_management? %> <%= sort_header_tag("score", :caption => l(:caption_score), :default_order => 'desc') if allowed_management? %>
<%= sort_header_tag("#{Deliverable.table_name}.subject", :caption => 'Subject') %> <%= sort_header_tag("#{Deliverable.table_name}.subject", :caption => l(:caption_subject)) %>
<%= sort_header_tag("labor_budget", :caption => 'Budget') if allowed_management? %> <%= sort_header_tag("total_budget", :caption => l(:caption_budget)) if allowed_management? %>
<%= sort_header_tag("spent", :caption => 'Spent') if allowed_management? %> <%= sort_header_tag("labor_budget", :caption => l(:caption_labor_budget)) if allowed_management? %>
<%= sort_header_tag("#{Deliverable.table_name}.due", :caption => 'Due') %> <%= sort_header_tag("spent", :caption => l(:caption_spent)) if allowed_management? %>
<%= sort_header_tag("progress", :caption => 'Progress') %> <%= sort_header_tag("#{Deliverable.table_name}.due", :caption => l(:caption_due)) %>
<%= sort_header_tag("progress", :caption => l(:caption_progress)) %>
<%= Redmine::Hook.call_hook(:plugin_budget_view_deliverable_list_header, { }) %>
</tr></thead> </tr></thead>
<tbody> <tbody>
<% deliverables.each do |deliverable| -%> <% deliverables.each do |deliverable| -%>

View File

@@ -1,3 +1,3 @@
<h3>Budget</h3> <h3><%= l(:budget_title) %></h3>
<%= link_to l(:label_issue_view_all), { :controller => 'issues', :action => 'index', :project_id => @project, :set_filter => 1 } %><br /> <%= link_to_function l(:label_new_deliverable), "$('new-deliverable').toggle();" if allowed_management? %><br />
<%= link_to_function "New Deliverable", "$('new-deliverable').toggle();" if allowed_management? %><br /> <%= link_to_function l(:label_toggle_all), "toggleAll();" if allowed_management? %><br />

View File

@@ -1,4 +1,4 @@
<h2>Update Deliverable</h2> <h2><%= l(:label_update_deliverable) %></h2>
<% form_for :deliverable, @deliverable, :url => {:controller => 'deliverables', :action => 'update', :id => @project, :deliverable_id => @deliverable.id }, <% form_for :deliverable, @deliverable, :url => {:controller => 'deliverables', :action => 'update', :id => @project, :deliverable_id => @deliverable.id },
:method => :post, :builder => TabularFormBuilder, :lang => current_language, :method => :post, :builder => TabularFormBuilder, :lang => current_language,
:html => {:multipart => true, :id => 'deliverable-form', :class => 'tabular'} do |f| %> :html => {:multipart => true, :id => 'deliverable-form', :class => 'tabular'} do |f| %>
@@ -8,4 +8,5 @@
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= stylesheet_link_tag "budget.css", :plugin => "budget_plugin", :media => "screen" %> <%= stylesheet_link_tag "budget.css", :plugin => "budget_plugin", :media => "screen" %>
<%= javascript_include_tag('budget', :plugin => 'budget_plugin') %>
<% end %> <% end %>

View File

@@ -3,8 +3,8 @@
</div> </div>
<% if allowed_management? %> <% if allowed_management? %>
<div id="new-deliverable" style="display:none;"> <div id="new-deliverable" style="<%= @display_form ? '' : 'display:none;' -%>">
<h2>New Deliverable</h2> <h2><%= l(:label_new_deliverable) %></h2>
<% remote_form_for :deliverable, @deliverable, :url => {:controller => 'deliverables', :action => 'create', :id => @project }, <% remote_form_for :deliverable, @deliverable, :url => {:controller => 'deliverables', :action => 'create', :id => @project },
:method => :post, :builder => TabularFormBuilder, :lang => current_language, :method => :post, :builder => TabularFormBuilder, :lang => current_language,
:html => {:multipart => true, :id => 'deliverable-form', :class => 'tabular'} do |f| %> :html => {:multipart => true, :id => 'deliverable-form', :class => 'tabular'} do |f| %>
@@ -28,4 +28,147 @@
<% content_for :header_tags do %> <% content_for :header_tags do %>
<%= stylesheet_link_tag "budget.css", :plugin => "budget_plugin", :media => "screen" %> <%= stylesheet_link_tag "budget.css", :plugin => "budget_plugin", :media => "screen" %>
<% javascript_tag do %>
/* Used to calculate the Budget */
var BudgetModule = Class.create();
Object.extend(BudgetModule.prototype, {
initialize: function () {},
toAmount: function(value) {
var amount = value.replace(/[^1234567890.]/ig,'');
if (amount) {
return parseFloat(amount);
} else {
return 0;
}
},
updateAmounts: function() {
if ($('deliverable_type').checked) {
// Fixed cost
var cost = Budget.toAmount($('deliverable_fixed_cost').value);
Budget.updateAmount($('fixedCost'), cost);
} else {
// Variable cost
var perHour = Budget.toAmount($('deliverable_cost_per_hour').value);
var hours = Budget.toAmount($('deliverable_total_hours').value);
var cost = perHour * hours;
Budget.updateAmount($('variableCost'), cost);
}
if ($('deliverable_overhead').value.match('%')) {
var overhead_subtotal = (Budget.toAmount($('deliverable_overhead').value) / 100) * cost;
} else {
var overhead_subtotal = Budget.toAmount($('deliverable_overhead').value);
}
if ($('deliverable_materials').value.match('%')) {
var materials_subtotal = (Budget.toAmount($('deliverable_materials').value) / 100) * cost;
} else {
var materials_subtotal = Budget.toAmount($('deliverable_materials').value);
}
// Profit uses labor cost and overhead
if ($('deliverable_profit').value.match('%')) {
var profit_subtotal = (Budget.toAmount($('deliverable_profit').value) / 100) * (cost + overhead_subtotal);
} else {
var profit_subtotal = Budget.toAmount($('deliverable_profit').value);
}
// Amounts
Budget.updateAmount($('overhead_subtotal'), overhead_subtotal);
Budget.updateAmount($('materials_subtotal'), materials_subtotal);
Budget.updateAmount($('profit_subtotal'), profit_subtotal);
var total = cost + overhead_subtotal + materials_subtotal + profit_subtotal;
$('deliverable_budget').value = total;
$('total-budget-calculation').innerHTML = Budget.number_to_currency(total);
},
updateAmount: function(element, value) {
if (element) {
element.innerHTML = Budget.number_to_currency(value);
}
},
changeType: function() {
if ($('deliverable_type').checked) {
// Fixed
$$('.budget-hourly').each(function(ele) { ele.hide(); });
$$('.budget-fixed').each(function(ele) { ele.show(); });
} else {
// Variable
$$('.budget-hourly').each(function(ele) { ele.show(); });
$$('.budget-fixed').each(function(ele) { ele.hide(); });
}
Budget.updateAmounts();
},
// Rails-like number_to_currency currency formatting
// http://snippets.dzone.com/posts/show/4646
number_to_currency: function (number, options) {
try {
var options = options || {};
var precision = options["precision"] || 2;
var unit = options["unit"] || "<%= number_to_currency(0, :precision => 0).gsub('0', '') %>";
var separator = precision > 0 ? options["separator"] || "." : "";
var delimiter = options["delimiter"] || " ";
var parts = parseFloat(number).toFixed(precision).split('.');
<% if I18n.locale != :en %>
<%= "return Budget.number_with_delimiter(parts[0], delimiter) + separator + parts[1].toString() + unit;" %>
<% else %>
<%= "return unit + Budget.number_with_delimiter(parts[0], delimiter) + separator + parts[1].toString();" %>
<% end %>
} catch(e) {
return number;
}
},
number_with_delimiter: function (number, delimiter, separator) {
try {
var delimiter = delimiter || ",";
var separator = separator || ".";
var parts = number.toString().split('.');
parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter);
return parts.join(separator);
} catch(e) {
return number
}
}
});
Budget = new BudgetModule();
function toggleAll() {
$$('.deliverable-details').each(function(ele) {
ele.toggle();
});
$$('.toggle').each(function(e) {
e.toggle();
});
}
function expandRow(deliverable_id) {
$('deliverable-details-'+ deliverable_id).show();
$('deliverable-description-'+ deliverable_id).show();
$$('.toggle_' + deliverable_id).each(function(e) {
e.toggle();
});
}
function collapseRow(deliverable_id) {
$('deliverable-details-'+ deliverable_id).hide();
$('deliverable-description-'+ deliverable_id).hide();
$$('.toggle_' + deliverable_id).each(function(e) {
e.toggle();
});
}
<% end %>
<% end %> <% end %>

View File

@@ -1,16 +1,16 @@
<p> <p>
Enter a dollar amount or percentage into each field to set your default amount. Use <strong>%</strong> in the field for percentages. <%= l(:message_budget_settings) %>
</p> </p>
<p> <p>
<label>Non billable overhead</label><%= text_field_tag 'settings[budget_non_billable_overhead]', @settings['budget_non_billable_overhead'], :size => 15 %> <label><%= l(:label_non_billable_overhead) %></label><%= text_field_tag 'settings[budget_non_billable_overhead]', @settings['budget_non_billable_overhead'], :size => 15 %>
</p> </p>
<p> <p>
<label>Materials</label><%= text_field_tag 'settings[budget_materials]', @settings['budget_materials'], :size => 15 %> <label><%= l(:label_materials) %></label><%= text_field_tag 'settings[budget_materials]', @settings['budget_materials'], :size => 15 %>
</p> </p>
<p> <p>
<label>Profit</label><%= text_field_tag 'settings[budget_profit]', @settings['budget_profit'], :size => 15 %> <label><%= l(:label_profit) %></label><%= text_field_tag 'settings[budget_profit]', @settings['budget_profit'], :size => 15 %>
</p> </p>

View File

@@ -0,0 +1,134 @@
/* Used to calculate the Budget */
var BudgetModule = Class.create();
Object.extend(BudgetModule.prototype, {
initialize: function () {},
toAmount: function(value) {
var amount = value.replace(/[^1234567890.]/ig,'');
if (amount) {
return parseFloat(amount);
} else {
return 0;
}
},
updateAmounts: function() {
if ($('deliverable_type').checked) {
// Fixed cost
var cost = Budget.toAmount($('deliverable_fixed_cost').value);
Budget.updateAmount($('fixedCost'), cost);
} else {
// Variable cost
var perHour = Budget.toAmount($('deliverable_cost_per_hour').value);
var hours = Budget.toAmount($('deliverable_total_hours').value);
var cost = perHour * hours;
Budget.updateAmount($('variableCost'), cost);
}
if ($('deliverable_overhead').value.match('%')) {
var overhead_subtotal = (Budget.toAmount($('deliverable_overhead').value) / 100) * cost;
} else {
var overhead_subtotal = Budget.toAmount($('deliverable_overhead').value);
}
if ($('deliverable_materials').value.match('%')) {
var materials_subtotal = (Budget.toAmount($('deliverable_materials').value) / 100) * cost;
} else {
var materials_subtotal = Budget.toAmount($('deliverable_materials').value);
}
// Profit uses labor cost and overhead
if ($('deliverable_profit').value.match('%')) {
var profit_subtotal = (Budget.toAmount($('deliverable_profit').value) / 100) * (cost + overhead_subtotal);
} else {
var profit_subtotal = Budget.toAmount($('deliverable_profit').value);
}
// Amounts
Budget.updateAmount($('overhead_subtotal'), overhead_subtotal);
Budget.updateAmount($('materials_subtotal'), materials_subtotal);
Budget.updateAmount($('profit_subtotal'), profit_subtotal);
var total = cost + overhead_subtotal + materials_subtotal + profit_subtotal;
$('deliverable_budget').value = total;
$('total-budget-calculation').innerHTML = Budget.number_to_currency(total);
},
updateAmount: function(element, value) {
if (element) {
element.innerHTML = Budget.number_to_currency(value);
}
},
changeType: function() {
if ($('deliverable_type').checked) {
// Fixed
$$('.budget-hourly').each(function(ele) { ele.hide(); });
$$('.budget-fixed').each(function(ele) { ele.show(); });
} else {
// Variable
$$('.budget-hourly').each(function(ele) { ele.show(); });
$$('.budget-fixed').each(function(ele) { ele.hide(); });
}
Budget.updateAmounts();
},
// Rails-like number_to_currency currency formatting
// http://snippets.dzone.com/posts/show/4646
number_to_currency: function (number, options) {
try {
var options = options || {};
var precision = options["precision"] || 2;
var unit = options["unit"] || " Kč";
var separator = precision > 0 ? options["separator"] || "." : "";
var delimiter = options["delimiter"] || " ";
var parts = parseFloat(number).toFixed(precision).split('.');
return Budget.number_with_delimiter(parts[0], delimiter) + separator + parts[1].toString() + unit;
} catch(e) {
return number;
}
},
number_with_delimiter: function (number, delimiter, separator) {
try {
var delimiter = delimiter || ",";
var separator = separator || ".";
var parts = number.toString().split('.');
parts[0] = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter);
return parts.join(separator);
} catch(e) {
return number
}
}
});
Budget = new BudgetModule();
function toggleAll() {
$$('.deliverable-details').each(function(ele) {
ele.toggle();
});
$$('.toggle').each(function(e) {
e.toggle();
});
}
function expandRow(deliverable_id) {
$('deliverable-details-'+ deliverable_id).show();
$('deliverable-description-'+ deliverable_id).show();
$$('.toggle_' + deliverable_id).each(function(e) {
e.toggle();
});
}
function collapseRow(deliverable_id) {
$('deliverable-details-'+ deliverable_id).hide();
$('deliverable-description-'+ deliverable_id).hide();
$$('.toggle_' + deliverable_id).each(function(e) {
e.toggle();
});
}

View File

@@ -20,7 +20,7 @@ tr.deliverable-details td { padding-bottom: 50px ; }
td.deliverable-actions { padding-left: 10px; width:100px;} td.deliverable-actions { padding-left: 10px; width:100px;}
td.deliverable-actions { padding-left: 10px; width:100px;} td.deliverable-actions { padding-left: 10px; width:100px;}
tr.deliverable-details { border: none; } tr.deliverable-details { border: none; white-space: normal; }
tr.deliverable-details p { text-align:left; clear:both;} tr.deliverable-details p { text-align:left; clear:both;}
tr.deliverable-details div { text-align:left; } tr.deliverable-details div { text-align:left; }
tr.deliverable-details table { width: 85%; } tr.deliverable-details table { width: 85%; }
@@ -37,7 +37,7 @@ tr.deliverable-details table tr td.data { text-align:right; }
td.calculation-column { text-align:right; width: auto; } td.calculation-column { text-align:right; width: auto; }
.budget-calculation { color:#999999; font-weight:bold; display:block; text-align:right; width: 100%;} .budget-calculation { color:#999999; font-weight:bold; display:block; text-align:right; width: 100%;}
p.total-budget { font-weight: bold; } p.total-budget, .total { font-weight: bold; }
p.total-budget #total-budget-calculation { } p.total-budget #total-budget-calculation { }
@@ -46,4 +46,4 @@ p.total-budget #total-budget-calculation { }
.jstElements button { padding: 0; margin-right: 0px;} .jstElements button { padding: 0; margin-right: 0px;}
.tabular div.splitcontentright label { margin-left: 0; float: none;} .tabular div.splitcontentright label { margin-left: 0; float: none;}
.tabular div.splitcontentright p { padding-left: 80px; } .tabular div.splitcontentright p { padding-left: 80px; }

101
budget_plugin.gemspec Normal file
View File

@@ -0,0 +1,101 @@
# Generated by jeweler
# DO NOT EDIT THIS FILE
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{budget_plugin}
s.version = "0.2.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Eric Davis"]
s.date = %q{2009-10-13}
s.description = %q{A plugin for Redmine to manage the set of deliverables for each project, automatically calculating key performance indicators.}
s.email = %q{edavis@littlestreamsoftware.com}
s.extra_rdoc_files = [
"README.rdoc"
]
s.files = [
"COPYRIGHT.txt",
"CREDITS.txt",
"GPL.txt",
"README.rdoc",
"Rakefile",
"TAGS",
"VERSION",
"app/controllers/deliverables_controller.rb",
"app/controllers/empty",
"app/helpers/deliverables_helper.rb",
"app/models/budget.rb",
"app/models/deliverable.rb",
"app/models/empty",
"app/models/fixed_deliverable.rb",
"app/models/hourly_deliverable.rb",
"app/models/member_spent.rb",
"app/views/deliverables/_budget.html.erb",
"app/views/deliverables/_deliverable.html.erb",
"app/views/deliverables/_deliverable_description_row.html.erb",
"app/views/deliverables/_deliverable_details_row.html.erb",
"app/views/deliverables/_deliverable_summary_row.html.erb",
"app/views/deliverables/_form.html.erb",
"app/views/deliverables/_list.html.erb",
"app/views/deliverables/_sidebar.html.erb",
"app/views/deliverables/create.js.rjs",
"app/views/deliverables/create_error.js.rjs",
"app/views/deliverables/edit.html.erb",
"app/views/deliverables/index.html.erb",
"app/views/deliverables/semantic.cache",
"app/views/empty",
"app/views/settings/_budget_settings.rhtml",
"assets/images/empty",
"assets/images/header.png",
"assets/images/toggle-arrow-closed.gif",
"assets/images/toggle-arrow-open.gif",
"assets/javascripts/budget.js",
"assets/javascripts/empty",
"assets/stylesheets/budget.css",
"assets/stylesheets/empty",
"config/locales/ca.yml",
"config/locales/en.yml",
"config/locales/es.yml",
"config/locales/hu.yml",
"config/locales/lt.yml",
"init.rb",
"lang/ca.yml",
"lang/en.yml",
"lang/es.yml",
"lang/hu.yml",
"lang/lt.yml",
"lib/budget_issue_hook.rb",
"lib/budget_project_hook.rb",
"lib/issue_patch.rb",
"lib/query_patch.rb",
"lib/tasks/empty",
"rails/init.rb"
]
s.homepage = %q{https://projects.littlestreamsoftware.com/projects/redmine-budget}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubyforge_project = %q{budget_plugin}
s.rubygems_version = %q{1.3.5}
s.summary = %q{A plugin for Redmine to manage the set of deliverables for each project, automatically calculating key performance indicators.}
s.test_files = [
"spec/spec_helper.rb",
"spec/models/deliverable_spec.rb",
"spec/models/fixed_deliverable_spec.rb",
"spec/models/budget_spec.rb",
"spec/models/hourly_deliverable_spec.rb",
"spec/controllers/deliverables_controller_spec.rb",
"spec/sanity_spec.rb"
]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
else
end
else
end
end

48
config/locales/ca.yml Normal file
View File

@@ -0,0 +1,48 @@
ca:
budget_title: Pressupost
field_cost_per_hour: Cost per hora
field_total_hours: Total hores
field_overhead: Imprevists
label_overhead: "Imprevists: "
field_materials: Cost dels materials
label_materials: "Materials: "
field_profit: Benefici
field_budget: Pressupost total
label_budget: "Pressupost total: "
field_fixed_cost: Preu tancat
field_project_manager_signoff: Vistiplau cap projecte
field_client_signoff: Vistiplau client
field_deliverable: Entregable
field_deliverable_subject: Entregable
field_due: Data finalització
label_member_rate: Tarifa (EUR)
label_currency:
message_updated_issues: Actualitzats %d assumptes
message_settings: Introduïu una quantitat en euros o un percentatge en cada camp per a establir un valor per defecte. Utilitzeu <strong>%%</strong> per indicar percentatges.
label_non_billable_overhead: Imprevists no facturables
label_materials: Materials
label_profit: Benefici
label_new_deliverable: Nou entregable
label_fixed_cost: Cost fix
caption_due: Entrega
caption_progress: Progrés
caption_subject: Assumpte
caption_score: Puntuació
caption_budget: Pressupost
caption_spent: Gastat
label_update_deliverable: Actualitzar entregable
label_labor_budget: "Pressupost mà d'obra: "
label_labor_budget_spent: "Pressupost gastat mà d'obra: "
label_labor_budget_remaining: "Pressupost romanent mà d'obra: "
label_progress: "Progrés: "
label_budget_score: "Puntuació pressupost: "
label_overruns: "Excessos: "
label_missing_on: Falta en
label_next_due_date: "Següent data d'entrega: "
label_completion: "Finalització: "
label_potential_profit: "Benefici potencial: "
label_bulk_assign: Assignar tots
label_labor: "Mà d'obra: "
label_fixed_amount: "Quantitat fixa: "
label_hours_estimated: "Hores estimades: "
label_hours_used: "Hores gastades: "

59
config/locales/cs.yml Normal file
View File

@@ -0,0 +1,59 @@
cs:
project_module_budget_module: "Rozpočet"
budget_title: Rozpočet
field_cost_per_hour: Cena za hodinu
field_total_hours: Celkem hodin
field_overhead: Strop
label_overhead: "Strop: "
field_materials: Cena materiálu
label_materials: "Materiály: "
field_profit: Zisk
field_budget: Celkový rozpočet
label_budget: "Celkový rozpočet: "
field_fixed_cost: Pevná cena
field_project_manager_signoff: Značka projektového manažera
field_client_signoff: Značka klienta
field_deliverable: K dodání
field_deliverable_subject: K dodání
field_due: Splatnost
label_member_rate: Zhodnocení (Kč)
label_currency:
message_updated_issues: upraveno %d úkolů
message_budget_settings: Vložte množství v korunách, nebo procento do každého z polí k nastavení výchozí hodnoty množství. Použijte <strong>%%</strong> v poli pro procenta.
label_non_billable_overhead: Nezúčtovatelné režie
label_materials: Materiály
label_profit: Zisk
label_new_deliverable: Nové dodání
label_fixed_cost: Pevná cena
caption_due: Splatnost
caption_progress: Postup
caption_subject: Předmět
caption_score: Hodnocení
caption_budget: Rozpočet
caption_labor_budget: Rozpočet práce
caption_spent: Vyčerpáno
label_update_deliverable: Upravit dodání
label_labor_budget: "Rozpočet práce: "
label_labor_budget_spent: "Vyčerpaný rozpočet práce: "
label_labor_budget_remaining: "Zbývající rozpočet práce: "
label_progress: "Postup: "
label_budget_score: "Hodnocení rozpočtu: "
label_overruns: "Překročení: "
label_missing_on: Schází
label_next_due_date: "Další datum splatnosti: "
label_completion: "Dokončení: "
label_potential_profit: "Potenciální zisk: "
label_bulk_assign: Hromadné přiřazení
label_labor: "Práce: "
label_fixed_amount: "Pevné množství: "
label_hours_estimated: "Hodinový odhad: "
label_hours_used: "Hodin využito: "
label_toggle_all: "Rozbalit všechny řádky s dodáním"
permission_view_budget: "Zobrazit rozpočet"
permission_manage_budget: "Spravovat rozpočet"
number:
currency:
format:
format: "%n %u"
unit: "Kč"

50
config/locales/en-GB.yml Normal file
View File

@@ -0,0 +1,50 @@
en-GB:
budget_title: Budget
field_cost_per_hour: Cost per hour
field_total_hours: Total hours
field_overhead: Overhead
label_overhead: "Overhead: "
field_materials: Material costs
label_materials: "Materials: "
field_profit: Profit
field_budget: Total Budget
label_budget: "Total Budget: "
field_fixed_cost: Fixed Bid
field_project_manager_signoff: Project Manager Signoff
field_client_signoff: Client Signoff
field_deliverable: Deliverable
field_deliverable_subject: Deliverable
field_due: Due Date
label_member_rate: Rate (£)
label_currency: £
message_updated_issues: Updated %d issues
message_budget_settings: Enter an amount in pounds, or a percentage into each field to set your default amount. Use <strong>%%</strong> in the field for percentages.
label_non_billable_overhead: Non billable overhead
label_materials: Materials
label_profit: Profit
label_new_deliverable: New deliverable
label_fixed_cost: Fixed cost
caption_due: Due
caption_progress: Progress
caption_subject: Subject
caption_score: Score
caption_budget: Budget
caption_labor_budget: Labor Budget
caption_spent: Spent
label_update_deliverable: Update Deliverable
label_labor_budget: "Labor Budget: "
label_labor_budget_spent: "Labor Budget Spent: "
label_labor_budget_remaining: "Labor Budget Remaining: "
label_progress: "Progress: "
label_budget_score: "Budget Score: "
label_overruns: "Overruns: "
label_missing_on: Missing on
label_next_due_date: "Next Due Date: "
label_completion: "Completion: "
label_potential_profit: "Potential Profit: "
label_bulk_assign: Bulk Assign
label_labor: "Labor: "
label_fixed_amount: "Fixed Amount: "
label_hours_estimated: "Hours Estimated: "
label_hours_used: "Hours Used: "
label_toggle_all: "Expand all deliverable rows"

50
config/locales/en.yml Normal file
View File

@@ -0,0 +1,50 @@
en:
budget_title: Budget
field_cost_per_hour: Cost per hour
field_total_hours: Total hours
field_overhead: Overhead
label_overhead: "Overhead: "
field_materials: Material costs
label_materials: "Materials: "
field_profit: Profit
field_budget: Total Budget
label_budget: "Total Budget: "
field_fixed_cost: Fixed Bid
field_project_manager_signoff: Project Manager Signoff
field_client_signoff: Client Signoff
field_deliverable: Deliverable
field_deliverable_subject: Deliverable
field_due: Due Date
label_member_rate: Rate ($)
label_currency: $
message_updated_issues: Updated %d issues
message_budget_settings: Enter an amount in dollars, or a percentage into each field to set your default amount. Use <strong>%%</strong> in the field for percentages.
label_non_billable_overhead: Non billable overhead
label_materials: Materials
label_profit: Profit
label_new_deliverable: New deliverable
label_fixed_cost: Fixed cost
caption_due: Due
caption_progress: Progress
caption_subject: Subject
caption_score: Score
caption_budget: Budget
caption_labor_budget: Labor Budget
caption_spent: Spent
label_update_deliverable: Update Deliverable
label_labor_budget: "Labor Budget: "
label_labor_budget_spent: "Labor Budget Spent: "
label_labor_budget_remaining: "Labor Budget Remaining: "
label_progress: "Progress: "
label_budget_score: "Budget Score: "
label_overruns: "Overruns: "
label_missing_on: Missing on
label_next_due_date: "Next Due Date: "
label_completion: "Completion: "
label_potential_profit: "Potential Profit: "
label_bulk_assign: Bulk Assign
label_labor: "Labor: "
label_fixed_amount: "Fixed Amount: "
label_hours_estimated: "Hours Estimated: "
label_hours_used: "Hours Used: "
label_toggle_all: "Expand all deliverable rows"

47
config/locales/es.yml Normal file
View File

@@ -0,0 +1,47 @@
es:
budget_title: Presupuestos
field_cost_per_hour: Coste por hora
field_total_hours: Total horas
field_overhead: Imprevistos
label_overhead: "Imprevistos: "
field_materials: Coste de los materiales
label_materials: "Materiales: "
field_profit: Beneficio
field_budget: Presupuesto total
label_budget: "Presupuesto total: "
field_fixed_cost: Precio cerrado
field_project_manager_signoff: Visto bueno jefe proyecto
field_client_signoff: Visto bueno cliente
field_deliverable: Entregable
field_deliverable_subject: Entregable
field_due: Fecha entrega
label_member_rate: Tarifa (EUR)
label_currency:
message_updated_issues: Actualizadas %d peticiones
message_settings: Introduce cantidades en euros o porcentajes en cada campo para establecer los valores por defecto. Usa <strong>%%</strong> para indicar porcentajes.
label_non_billable_overhead: Imprevistos no facturables
label_profit: Beneficio
label_new_deliverable: Nuevo entregable
label_fixed_cost: Coste fijo
caption_due: Entrega
caption_progress: Progreso
caption_subject: Asunto
caption_score: Importancia
caption_budget: Presupuesto
caption_spent: Gastado
label_update_deliverable: Actualizar entregable
label_labor_budget: "Presupuesto mano de obra: "
label_labor_budget_spent: "Presupuesto gastado mano de obra: "
label_labor_budget_remaining: "Presupuesto remanente mano de obra: "
label_progress: "Progreso: "
label_budget_score: "Puntuación presupuesto: "
label_overruns: "Excesos: "
label_missing_on: Falta en
label_next_due_date: "Siguiente entrega: "
label_completion: "Finalización: "
label_potential_profit: "Beneficio potencial: "
label_bulk_assign: Asignar todos
label_labor: "Mano de obra: "
label_fixed_amount: "Cantidad fija: "
label_hours_estimated: "Horas estimadas: "
label_hours_used: "Horas usadas: "

50
config/locales/fr.yml Normal file
View File

@@ -0,0 +1,50 @@
fr:
budget_title: Budget
field_cost_per_hour: Coût par heure
field_total_hours: Total heures
field_overhead: Frais géneraux
label_overhead: "Frais géneraux: "
field_materials: Cout des matières premières
label_materials: "Cout des matières premières: "
field_profit: Profit
field_budget: Budget total
label_budget: "Budget total: "
field_fixed_cost: Coût fixe
field_project_manager_signoff: Validation par le gestionnaire de projet
field_client_signoff: Validation par le client
field_deliverable: Livrable
field_deliverable_subject: Livrable
field_due: "Date d'echeance"
label_member_rate: Tarif (€)
label_currency:
message_updated_issues: %d demandes mises à jour
message_budget_settings: Entrez un montant en euros ou un pourcentage dans chaque champs pour régler le montant par defaut. Utilisez <strong>%%</strong> dans le champ pour des pourcentages.
label_non_billable_overhead: Frais géneraux non facturables
label_materials: Matières premières
label_profit: Profit
label_new_deliverable: Nouveau livrable
label_fixed_cost: Coût fixe
caption_due:
caption_progress: Progressions
caption_subject: Objet
caption_score: Score
caption_budget: Budget
caption_labor_budget: Budget de travail
caption_spent: Dépense
label_update_deliverable: Mettre à jour le livrable
label_labor_budget: "Budget de travail: "
label_labor_budget_spent: "Budget de travail depensé: "
label_labor_budget_remaining: "Budget de travail restant: "
label_progress: "Progression: "
label_budget_score: "Score du budget: "
label_overruns: "Dépassement: "
label_missing_on: Manquant sur
label_next_due_date: "Prochaine date d'échéance: "
label_completion: "Completion: "
label_potential_profit: "Potential Profit: "
label_bulk_assign: Assigner en vrac
label_labor: "Travail: "
label_fixed_amount: "Montant fixe: "
label_hours_estimated: "Heures estimées: "
label_hours_used: "Heures utilisées: "
label_toggle_all: "Développer tous les livrables"

16
config/locales/hu.yml Normal file
View File

@@ -0,0 +1,16 @@
hu:
field_cost_per_hour: Óradíj
field_total_hours: Összes órák
field_overhead: Felfüggesztett
field_materials: Anyag költség
field_profit: Profit
field_budget: Teljes költség
field_fixed_cost: Rögzített árajánlat
field_project_manager_signoff: Projekt felelős által aláíratlan
field_client_signoff: Ügyfél által aláíratlan
field_deliverable: Teljesítés
field_deliverable_subject: Teljesítés
field_due: Esedékesség dátuma
label_member_rate: Részesedés ($)
label_currency: $
message_updated_issues: Frissítve % feladat

16
config/locales/lt.yml Normal file
View File

@@ -0,0 +1,16 @@
lt:
field_cost_per_hour: Valandinis užmokestis
field_total_hours: Iš viso valandų
field_overhead: Viršyjimas
field_materials: Material costs
field_profit: Pelnas
field_budget: Visas biudžetas
field_fixed_cost: Patovus pasiūlymas
field_project_manager_signoff: Pasirašo projekto vadovas
field_client_signoff: Pasirašo klientas
field_deliverable: Pateiktis
field_deliverable_subject: Pateiktis
field_due: Data iki
label_member_rate: Užmokestis (LTL)
label_currency: $
message_updated_issues: Atnaujinta(i) %d darbų(ai)

31
init.rb
View File

@@ -1,19 +1,35 @@
require 'redmine' require 'redmine'
# Patches to the Redmine core. Will not work in development mode # Budget requires the Rate plugin
require_dependency 'issue_patch' begin
require_dependency 'query_patch' require 'rate' unless Object.const_defined?('Rate')
rescue LoadError
# rate_plugin is not installed
raise Exception.new("ERROR: The Rate plugin is not installed. Please install the Rate plugin from https://projects.littlestreamsoftware.com/projects/redmine-rate")
end
# Patches to the Redmine core.
require 'dispatcher'
require 'issue_patch'
require 'query_patch'
Dispatcher.to_prepare do
Issue.send(:include, IssuePatch) unless Issue.included_modules.include? IssuePatch
Query.send(:include, QueryPatch) unless Query.included_modules.include? QueryPatch
end
# Hooks # Hooks
require_dependency 'budget_issue_hook' require_dependency 'budget_issue_hook'
require_dependency 'budget_project_hook'
RAILS_DEFAULT_LOGGER.info 'Starting Budget plugin for RedMine'
Redmine::Plugin.register :budget_plugin do Redmine::Plugin.register :budget_plugin do
name 'Budget' name 'Budget'
author 'Eric Davis <edavis@littlestreamsoftware.com>' author 'Eric Davis'
description 'Budget is a plugin to manage the set of deliverables for each project, automatically calculating key performance indicators.' description 'Budget is a plugin to manage the set of deliverables for each project, automatically calculating key performance indicators.'
url 'https://projects.littlestreamsoftware.com/projects/redmine-budget'
author_url 'http://www.littlestreamsoftware.com'
version '0.2.0' version '0.2.0'
requires_redmine :version_or_higher => '0.8.0'
settings :default => { settings :default => {
'budget_nonbillable_overhead' => '', 'budget_nonbillable_overhead' => '',
@@ -27,5 +43,6 @@ Redmine::Plugin.register :budget_plugin do
permission :manage_budget, { :deliverables => [:new, :edit, :create, :update, :destroy, :preview, :bulk_assign_issues]} permission :manage_budget, { :deliverables => [:new, :edit, :create, :update, :destroy, :preview, :bulk_assign_issues]}
end end
menu :project_menu, :budget, :controller => "deliverables", :action => 'index' menu :project_menu, :budget, {:controller => "deliverables", :action => 'index'}, :caption => :budget_title
end end
require 'redmine_budget/hooks/controller_timelog_available_criterias_hook'

46
lang/ca.yml Normal file
View File

@@ -0,0 +1,46 @@
budget_title: Pressupost
field_cost_per_hour: Cost per hora
field_total_hours: Total hores
field_overhead: Imprevists
label_overhead: "Imprevists: "
field_materials: Cost dels materials
label_materials: "Materials: "
field_profit: Benefici
field_budget: Pressupost total
label_budget: "Pressupost total: "
field_fixed_cost: Preu tancat
field_project_manager_signoff: Vistiplau cap projecte
field_client_signoff: Vistiplau client
field_deliverable: Entregable
field_deliverable_subject: Entregable
field_due: Data finalització
label_member_rate: Tarifa (EUR)
message_updated_issues: Actualitzats %d assumptes
message_settings: Introduïu una quantitat en euros o un percentatge en cada camp per a establir un valor per defecte. Utilitzeu <strong>%%</strong> per indicar percentatges.
label_non_billable_overhead: Imprevists no facturables
label_materials: Materials
label_profit: Benefici
label_new_deliverable: Nou entregable
label_fixed_cost: Cost fix
caption_due: Entrega
caption_progress: Progrés
caption_subject: Assumpte
caption_score: Puntuació
caption_budget: Pressupost
caption_spent: Gastat
label_update_deliverable: Actualitzar entregable
label_labor_budget: "Pressupost mà d'obra: "
label_labor_budget_spent: "Pressupost gastat mà d'obra: "
label_labor_budget_remaining: "Pressupost romanent mà d'obra: "
label_progress: "Progrés: "
label_budget_score: "Puntuació pressupost: "
label_overruns: "Excessos: "
label_missing_on: Falta en
label_next_due_date: "Següent data d'entrega: "
label_completion: "Finalització: "
label_potential_profit: "Benefici potencial: "
label_bulk_assign: Assignar tots
label_labor: "Mà d'obra: "
label_fixed_amount: "Quantitat fixa: "
label_hours_estimated: "Hores estimades: "
label_hours_used: "Hores gastades: "

View File

@@ -1,9 +1,13 @@
budget_title: Budget
field_cost_per_hour: Cost per hour field_cost_per_hour: Cost per hour
field_total_hours: Total hours field_total_hours: Total hours
field_overhead: Overhead field_overhead: Overhead
label_overhead: "Overhead: "
field_materials: Material costs field_materials: Material costs
label_materials: "Materials: "
field_profit: Profit field_profit: Profit
field_budget: Total Budget field_budget: Total Budget
label_budget: "Total Budget: "
field_fixed_cost: Fixed Bid field_fixed_cost: Fixed Bid
field_project_manager_signoff: Project Manager Signoff field_project_manager_signoff: Project Manager Signoff
field_client_signoff: Client Signoff field_client_signoff: Client Signoff
@@ -12,3 +16,33 @@ field_deliverable_subject: Deliverable
field_due: Due Date field_due: Due Date
label_member_rate: Rate ($) label_member_rate: Rate ($)
message_updated_issues: Updated %d issues message_updated_issues: Updated %d issues
message_budget_settings: Enter a dollar amount or percentage into each field to set your default amount. Use <strong>%%</strong> in the field for percentages.
label_non_billable_overhead: Non billable overhead
label_materials: Materials
label_profit: Profit
label_new_deliverable: New deliverable
label_fixed_cost: Fixed cost
caption_due: Due
caption_progress: Progress
caption_subject: Subject
caption_score: Score
caption_budget: Budget
caption_labor_budget: Labor Budget
caption_spent: Spent
label_update_deliverable: Update Deliverable
label_labor_budget: "Labor Budget: "
label_labor_budget_spent: "Labor Budget Spent: "
label_labor_budget_remaining: "Labor Budget Remaining: "
label_progress: "Progress: "
label_budget_score: "Budget Score: "
label_overruns: "Overruns: "
label_missing_on: Missing on
label_next_due_date: "Next Due Date: "
label_completion: "Completion: "
label_potential_profit: "Potential Profit: "
label_bulk_assign: Bulk Assign
label_labor: "Labor: "
label_fixed_amount: "Fixed Amount: "
label_hours_estimated: "Hours Estimated: "
label_hours_used: "Hours Used: "
label_toggle_all: "Expand all deliverable rows"

45
lang/es.yml Normal file
View File

@@ -0,0 +1,45 @@
budget_title: Presupuestos
field_cost_per_hour: Coste por hora
field_total_hours: Total horas
field_overhead: Imprevistos
label_overhead: "Imprevistos: "
field_materials: Coste de los materiales
label_materials: "Materiales: "
field_profit: Beneficio
field_budget: Presupuesto total
label_budget: "Presupuesto total: "
field_fixed_cost: Precio cerrado
field_project_manager_signoff: Visto bueno jefe proyecto
field_client_signoff: Visto bueno cliente
field_deliverable: Entregable
field_deliverable_subject: Entregable
field_due: Fecha entrega
label_member_rate: Tarifa (EUR)
message_updated_issues: Actualizadas %d peticiones
message_settings: Introduce cantidades en euros o porcentajes en cada campo para establecer los valores por defecto. Usa <strong>%%</strong> para indicar porcentajes.
label_non_billable_overhead: Imprevistos no facturables
label_profit: Beneficio
label_new_deliverable: Nuevo entregable
label_fixed_cost: Coste fijo
caption_due: Entrega
caption_progress: Progreso
caption_subject: Asunto
caption_score: Importancia
caption_budget: Presupuesto
caption_spent: Gastado
label_update_deliverable: Actualizar entregable
label_labor_budget: "Presupuesto mano de obra: "
label_labor_budget_spent: "Presupuesto gastado mano de obra: "
label_labor_budget_remaining: "Presupuesto remanente mano de obra: "
label_progress: "Progreso: "
label_budget_score: "Puntuación presupuesto: "
label_overruns: "Excesos: "
label_missing_on: Falta en
label_next_due_date: "Siguiente entrega: "
label_completion: "Finalización: "
label_potential_profit: "Beneficio potencial: "
label_bulk_assign: Asignar todos
label_labor: "Mano de obra: "
label_fixed_amount: "Cantidad fija: "
label_hours_estimated: "Horas estimadas: "
label_hours_used: "Horas usadas: "

View File

14
lang/hu.yml Normal file
View File

@@ -0,0 +1,14 @@
field_cost_per_hour: Óradíj
field_total_hours: Összes órák
field_overhead: Felfüggesztett
field_materials: Anyag költség
field_profit: Profit
field_budget: Teljes költség
field_fixed_cost: Rögzített árajánlat
field_project_manager_signoff: Projekt felelős által aláíratlan
field_client_signoff: Ügyfél által aláíratlan
field_deliverable: Teljesítés
field_deliverable_subject: Teljesítés
field_due: Esedékesség dátuma
label_member_rate: Részesedés ($)
message_updated_issues: Frissítve % feladat

14
lang/lt.yml Normal file
View File

@@ -0,0 +1,14 @@
field_cost_per_hour: Valandinis užmokestis
field_total_hours: Iš viso valandų
field_overhead: Viršyjimas
field_materials: Material costs
field_profit: Pelnas
field_budget: Visas biudžetas
field_fixed_cost: Patovus pasiūlymas
field_project_manager_signoff: Pasirašo projekto vadovas
field_client_signoff: Pasirašo klientas
field_deliverable: Pateiktis
field_deliverable_subject: Pateiktis
field_due: Data iki
label_member_rate: Užmokestis (LTL)
message_updated_issues: Atnaujinta(i) %d darbų(ai)

View File

@@ -38,15 +38,30 @@ class BudgetIssueHook < Redmine::Hook::ViewListener
def view_issues_bulk_edit_details_bottom(context = { }) def view_issues_bulk_edit_details_bottom(context = { })
if context[:project].module_enabled?('budget_module') if context[:project].module_enabled?('budget_module')
select = select_tag('deliverable_id', select = select_tag('deliverable_id',
content_tag('option', GLoc.l(:label_no_change_option), :value => '') + content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', GLoc.l(:label_none), :value => 'none') + content_tag('option', l(:label_none), :value => 'none') +
options_from_collection_for_select(Deliverable.find_all_by_project_id(context[:project].id, :order => 'subject ASC'), :id, :subject)) options_from_collection_for_select(Deliverable.find_all_by_project_id(context[:project].id, :order => 'subject ASC'), :id, :subject))
return content_tag(:p, "<label>#{GLoc.l(:field_deliverable)}: " + select + "</label>") return content_tag(:p, content_tag(:label, l(:field_deliverable)) + select)
else else
return '' return ''
end end
end end
def set_deliverable_on_issue(context)
if context[:params] && context[:params][:issue] && context[:params][:issue][:deliverable_id].present?
context[:issue].deliverable = Deliverable.find_by_id_and_project_id(context[:params][:issue][:deliverable_id].to_i, context[:issue].project.id)
end
return ''
end
def controller_issues_new_before_save(context = {})
set_deliverable_on_issue(context)
end
def controller_issues_edit_before_save(context = {})
set_deliverable_on_issue(context)
end
# Saves the Deliverable assignment to the issue # Saves the Deliverable assignment to the issue
# #

View File

@@ -0,0 +1,26 @@
class BudgetProjectHook < Redmine::Hook::ViewListener
def model_project_copy_before_save(context = {})
source = context[:source_project]
destination = context[:destination_project]
if source.module_enabled?(:budget_module)
Deliverable.find(:all, :conditions => {:project_id => source.id}).each do |source_deliverable|
destination_deliverable = source_deliverable.class.new # STI classes
# Copy attribute except for the ones that have wrapped
# accessors, use read/write attribute for them
destination_deliverable.attributes = source_deliverable.attributes.except("project_id", "profit", "materials", "overhead")
destination_deliverable.write_attribute(:profit, source_deliverable.read_attribute(:profit))
destination_deliverable.write_attribute(:profit_percent, source_deliverable.read_attribute(:profit_percent))
destination_deliverable.write_attribute(:materials, source_deliverable.read_attribute(:materials))
destination_deliverable.write_attribute(:materials_percent, source_deliverable.read_attribute(:materials_percent))
destination_deliverable.write_attribute(:overhead, source_deliverable.read_attribute(:overhead))
destination_deliverable.write_attribute(:overhead_percent, source_deliverable.read_attribute(:overhead_percent))
destination_deliverable.project = destination
destination_deliverable.save # Need to save here because there is no relation on project to deliverable
end
end
end
end

View File

@@ -32,7 +32,4 @@ module IssuePatch
end end
end end
# Add module to Issue
Issue.send(:include, IssuePatch)

View File

@@ -50,7 +50,4 @@ module QueryPatch
end end
end end
# Add module to Query
Query.send(:include, QueryPatch)

View File

@@ -0,0 +1,15 @@
module RedmineBudget
module Hooks
class ControllerTimelogAvailableCriteriasHook < Redmine::Hook::ViewListener
# Adds the Deliverable as a filter to the Timelog Report
def controller_timelog_available_criterias(context={})
context[:available_criterias]["deliverable_id"] = {
:sql => "#{Issue.table_name}.deliverable_id",
:klass => Deliverable,
:label => :field_deliverable
}
return ''
end
end
end
end

1
rails/init.rb Normal file
View File

@@ -0,0 +1 @@
require File.dirname(__FILE__) + "/../init"

View File

@@ -12,9 +12,10 @@ end
describe DeliverablesController,"#index when logged in" do describe DeliverablesController,"#index when logged in" do
before(:each) do before(:each) do
@project = mock_model(Project) @project = mock_model(Project)
@deliverable = mock_model(Deliverable)
Deliverable.stub!(:count).and_return(0) Deliverable.stub!(:count).and_return(1)
Deliverable.stub!(:find).and_return([]) Deliverable.stub!(:find).and_return([@deliverable])
Project.should_receive(:find).with(@project.to_param).and_return(@project) Project.should_receive(:find).with(@project.to_param).and_return(@project)
Project.should_receive(:find).with(@project.id).and_return(@project) Project.should_receive(:find).with(@project.id).and_return(@project)
@@ -30,6 +31,25 @@ describe DeliverablesController,"#index when logged in" do
get :index, :id => @project.id get :index, :id => @project.id
assigns[:deliverables].should_not be_nil assigns[:deliverables].should_not be_nil
end end
it "should set @display_form to false by default" do
get :index, :id => @project.id
assigns[:display_form].should eql(false)
end
it "should set @display_form to true if there are no deliverables" do
Deliverable.should_receive(:count).and_return(0)
Deliverable.should_receive(:find).and_return([])
get :index, :id => @project.id
assigns[:display_form].should eql(true)
end
it "should set @display_form to true if the 'new' parameter is used" do
get :index, :id => @project.id, :new => 'true'
assigns[:display_form].should eql(true)
end
it "should only show the deliverables for the current project only" do it "should only show the deliverables for the current project only" do
# TODO: Get spec working for full finder # TODO: Get spec working for full finder

View File

@@ -0,0 +1,55 @@
require File.dirname(__FILE__) + '/../spec_helper'
include Redmine::Hook::Helper
def controller
@controller ||= TimelogController.new
@controller.response ||= ActionController::TestResponse.new
@controller
end
def request
@request ||= ActionController::TestRequest.new
end
def run_hook
call_hook(:controller_timelog_available_criterias, {:available_criterias => @available_criterias})
end
describe TimelogController, '#controller_timelog_available_criterias_hook', :type => :controller do
before(:each) do
@available_criterias = {
'project' => {:sql => 'project_id', :klass => Project, :label => :label_project}
}
end
it "should always return an empty string" do
run_hook.should be_blank
end
it "should add a new hash to the available_criterias" do
run_hook
@available_criterias.should have(2).keys
@available_criterias.key?('deliverable_id').should be_true
end
it "should set the :sql field to use the issue's deliverable_id" do
run_hook
@available_criterias['deliverable_id'][:sql].should eql("issues.deliverable_id")
end
it "should set the :klass field to Deliverable" do
run_hook
@available_criterias['deliverable_id'][:klass].should eql(Deliverable)
end
it "should set the :label field to :field_deliverable" do
run_hook
@available_criterias['deliverable_id'][:label].should eql(:field_deliverable)
end
end