├── app
├── helpers
│ └── chakuchi_helper.rb
├── models
│ └── chakuchi_constants.rb
├── controllers
│ └── chakuchi_controller.rb
└── views
│ └── chakuchi
│ └── index.html.erb
├── assets
├── images
│ ├── roles_management.png
│ ├── chakuchi_dialog_en.png
│ ├── chakuchi_overview_en.png
│ └── chakuchi_overview_ja.png
├── javascripts
│ └── chakuchi.js
└── stylesheets
│ └── chakuchi.css
├── test
├── test_helper.rb
└── functional
│ └── chakuchi_controller_test.rb
├── config
├── routes.rb
└── locales
│ ├── ja.yml
│ └── en.yml
├── init.rb
├── LICENSE
└── README.md
/app/helpers/chakuchi_helper.rb:
--------------------------------------------------------------------------------
1 | module ChakuchiHelper
2 | end
3 |
--------------------------------------------------------------------------------
/assets/images/roles_management.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happy-se-life/chakuchi/master/assets/images/roles_management.png
--------------------------------------------------------------------------------
/assets/images/chakuchi_dialog_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happy-se-life/chakuchi/master/assets/images/chakuchi_dialog_en.png
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Load the Redmine helper
2 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
3 |
--------------------------------------------------------------------------------
/assets/images/chakuchi_overview_en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happy-se-life/chakuchi/master/assets/images/chakuchi_overview_en.png
--------------------------------------------------------------------------------
/assets/images/chakuchi_overview_ja.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/happy-se-life/chakuchi/master/assets/images/chakuchi_overview_ja.png
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # Plugin's routes
2 | # See: http://guides.rubyonrails.org/routing.html
3 | Rails.application.routes.draw do
4 | resources :chakuchi
5 | end
--------------------------------------------------------------------------------
/app/models/chakuchi_constants.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Constants
3 | #
4 | class ChakuchiConstants < ActiveRecord::Base
5 | # Working hours per day
6 | WORKING_HOURS_PER_DAY = 8
7 | end
8 |
--------------------------------------------------------------------------------
/test/functional/chakuchi_controller_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../../test_helper', __FILE__)
2 |
3 | class ChakuchiControllerTest < ActionController::TestCase
4 | # Replace this with your real tests.
5 | def test_truth
6 | assert true
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/assets/javascripts/chakuchi.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | $('[id^=user-dialog-]').dialog({
3 | title: "",
4 | width: 1080,
5 | autoOpen: false,
6 | modal: true,
7 | buttons: {
8 | "Close": function() {
9 | $(this).dialog("close");
10 | }
11 | }
12 | });
13 | })
14 |
--------------------------------------------------------------------------------
/assets/stylesheets/chakuchi.css:
--------------------------------------------------------------------------------
1 | table.user_dialog_table {
2 | text-align: center;
3 | border-spacing: 0px;
4 | }
5 |
6 | td.downward_size {
7 | width: 90px;
8 | height: 40px;
9 | }
10 |
11 | td.under_line {
12 | width: 90px;
13 | border-bottom: 2px black solid;
14 | }
15 |
16 | td.left_line {
17 | border-left: 2px black solid;
18 | }
19 |
20 | td.downward_line {
21 | background-image: linear-gradient(to top right, transparent, transparent 49%, black 49%, black 51%, transparent 51%, transparent);
22 | }
23 |
24 | td.spacer {
25 | width: 20px;
26 | }
27 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | Redmine::Plugin.register :chakuchi do
2 | name 'Chakuchi plugin'
3 | author 'Kohei Nomura'
4 | description 'Chakuchi plugin shows estimated completion date of the version.'
5 | version '0.0.1'
6 | url 'https://github.com/happy-se-life/chakuchi'
7 | author_url 'mailto:kohei_nom@yahoo.co.jp'
8 |
9 | # Display menu item at the project
10 | menu :project_menu, :display_tab, { :controller => 'chakuchi', :action => 'index' }, :caption => :chakuchi_menu_caption, :param => :project_id
11 |
12 | # Enable permission for the project
13 | project_module :chakuchi do
14 | permission :display_tab, {:chakuchi => [:index]}
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/config/locales/ja.yml:
--------------------------------------------------------------------------------
1 | # Japanese strings go here for Rails i18n
2 | ja:
3 | chakuchi_menu_caption: 着地
4 | chakuchi_label_title: 着地
5 | chakuchi_label_version_details: バージョン詳細
6 | chakuchi_label_user_details: メンバー詳細
7 | chakuchi_label_version_name: バージョン名
8 | chakuchi_label_start_date: 開始日
9 | chakuchi_label_due_date: 期日
10 | chakuchi_label_issues_count: チケット数
11 | chakuchi_label_closed_issues_count: 完了チケット数
12 | chakuchi_label_workdays_left: 残り営業日
13 | chakuchi_label_days_late: 遅れ日数
14 | chakuchi_label_estimated_completion_date: 着地日
15 | chakuchi_label_user_name: 名前
16 | chakuchi_label_estimated_hours: 予定工数
17 | chakuchi_label_estimated_hours_today: 今日までの予定工数
18 | chakuchi_label_spent_hours: 作業時間
19 | chakuchi_label_progress: 進捗率
20 | chakuchi_label_unit_days: 日
21 | chakuchi_label_total: 合計
22 | chakuchi_label_no_assignee: 未指定
23 | chakuchi_label_start_and_due_date: 開始日/期日
24 | chakuchi_label_today: 今日
25 | chakuchi_label_earned_value: 出来高
26 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # English strings go here for Rails i18n
2 | en:
3 | chakuchi_menu_caption: Chakuchi
4 | chakuchi_label_title: Chakuchi
5 | chakuchi_label_version_details: Version details
6 | chakuchi_label_user_details: User details
7 | chakuchi_label_version_name: Version Name
8 | chakuchi_label_start_date: Start Date
9 | chakuchi_label_due_date: Due Date
10 | chakuchi_label_issues_count: Issues
11 | chakuchi_label_closed_issues_count: Closed Issues
12 | chakuchi_label_workdays_left: Workdays Left
13 | chakuchi_label_days_late: Days late
14 | chakuchi_label_estimated_completion_date: Chakuchi Date
15 | chakuchi_label_user_name: User Name
16 | chakuchi_label_estimated_hours: Estimated Hours
17 | chakuchi_label_estimated_hours_today: Estimated Hours Today
18 | chakuchi_label_spent_hours: Spent Hours
19 | chakuchi_label_progress: '% Done'
20 | chakuchi_label_unit_days: d
21 | chakuchi_label_total: Total
22 | chakuchi_label_no_assignee: Not Assigned
23 | chakuchi_label_start_and_due_date: Start/Due Date
24 | chakuchi_label_today: Today
25 | chakuchi_label_earned_value: Earned hours
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 happy-se-life
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redmine chakuchi plugin
2 | This plugin shows estimated completion date of the project version.
3 | This is a trivia, Chakuchi is Japanese word means landing.
4 |
5 | ## What's new
6 | * Added a dialog for calculation check. See screenshot.
7 |
8 | ## Auther's comments
9 | * I bought an apple sillicon macbook air recently, so I developed this with great momentum. Lol
10 | * I think the EVM method is great, but recently I've been focusing on the number of days left until the completion date to manage the project.
11 | * Any question and request is welcome. Please write a issue on GitHub.
12 |
13 | ## Features
14 | * It is possible to know estimated completion date of the project version as of today.
15 | * It is possible to know the number of days late as of today.
16 | * Supports English and Japanese language.
17 | * Easy installation. No migration.
18 |
19 | ## Screenshots
20 |
21 | ### English overview
22 |
23 |
24 | ### Japanese overview
25 |
26 |
27 | ### Dialog for calculation check
28 |
29 |
30 | ## Description of each items
31 |
32 | ### Table of version details
33 | * Version Name : This is name of the version.
34 | * Start Date : This is the earliest start date of the tickets included in the version.
35 | * Due Date : This is due date of the version.
36 | * Issues : This is the number of tickets included in the version.
37 | * Closed Issues : This is the number of closed tickets included in the version.
38 | * Workdays Left : This is the number of business days from today to the due date of the version. Excludes Saturdays and Sundays.
39 | * Days late : This is the total number of days late for all members.
40 | * Chakuchi Date : This means estimated completion date of the version. Excludes Saturdays and Sundays.
41 |
42 | ### Table of user details
43 | * User Name : This is name of the member.
44 | * Issues : This is the number of tickets the member has.
45 | * Estimated Hours : This is total estimated hours of tickets the member has.
46 | * Spent Hours : This is total spent hours of tickets the member has.
47 | * % Done : This is average of progress rate of tickets the member has.
48 | * Days late : This is the number of days late as of today. The planned value as of today are calculated by linearly interpolating the estimated hours between the start date and the due date. Then, it is calculated from the value obtained by subtracting the earned value as of today. Plus value indicates delay. I'm doing it with a simplified EVM. This is clickable to show dialog.
49 | * Due Date : This is the latest due date of tickets the member has.
50 | * Chakuchi Date : This means estimated completion date of tickets the member has. Excludes Saturdays and Sundays.
51 |
52 | ## Requirments
53 | * Tickets : Enter the start date, due date and estimated hours as much as possible. It is not a required, but if you enter all of them, the accuracy will increase.
54 | * Version : Due date of the version is a required.
55 |
56 | ## How to install
57 |
58 | 1. Move to plugins folder.
59 |
60 | git clone https://github.com/happy-se-life/chakuchi.git 61 |62 | 63 | 2. Edit models/constants.rb for your environment. 64 | 65 | 3. Set the time zone. 66 | Example for Japanese : Add below a line into config/application.rb. 67 |
68 | config.time_zone = 'Asia/Tokyo' 69 |70 | 71 | 4. Restart redmine. 72 | 73 | 5. Enable role permission to each users groups 74 |
75 |
76 | 6. Enable modules for each project.
77 |
78 | ## How to uninstall
79 |
80 | 1. Move to plugins folder.
81 |
82 | 2. Remove plugins folder.
83 | 84 | rm -rf chakuchi 85 |86 | 87 | 3. Restart redmine. 88 | 89 | ## Weakpoints and Hints 90 | I don't think it's necessary to explain to an experienced project manager. 91 | * The planned value as of today is linearly interpolated. Therefore, the days late as of today may be inaccurate. It may or may not be reasonable. 92 | * The planned value is most important for accuracy. Don't be afraid to update estimated hours of a ticket frequently to take advantage of this plugin. 93 | * % Done is also important as it affects earned value. %done tends to be entered subjectively, and I think it should be linked with the status if possible. 94 | * So don't think too hard. The manager pays the latest attention to the members' minds, regardless of whether they are on the desk. 95 | * Working days exclude Saturdays and Sundays. I also want to consider the holidays of each country. 96 | 97 | ## License 98 | * MIT Lisense 99 | 100 | -------------------------------------------------------------------------------- /app/controllers/chakuchi_controller.rb: -------------------------------------------------------------------------------- 1 | class ChakuchiController < ApplicationController 2 | unloadable 3 | before_action :global_authorize 4 | 5 | def index 6 | 7 | # Discard session 8 | if params[:clear].to_i == 1 then 9 | discard_session 10 | end 11 | 12 | # Restore session 13 | restore_params_from_session 14 | 15 | # Initialize params 16 | initialize_params 17 | 18 | # Store session 19 | store_params_to_session 20 | 21 | # Create array of display user IDs 22 | @user_id_array = [] 23 | @user_id_array = @project.users.ids 24 | 25 | # Add Not Assigned 26 | @user_id_array << nil; 27 | 28 | # Hash for view 29 | @users_hash = {} 30 | @number_of_issues_hash = {} 31 | @start_date_hash = {} 32 | @due_date_hash = {} 33 | @total_estimated_hours_hash = {} 34 | @estimated_hours_today_hash = {} 35 | @total_spent_hours_hash = {} 36 | @earned_value_total_hash = {} 37 | @days_late_hash = {} 38 | @total_due_date_hash = {} 39 | @estimated_completion_date_hash = {} 40 | 41 | today = Time.now 42 | @today_date = today.strftime("%Y-%m-%d") 43 | 44 | if !@version.nil? then 45 | @workdays_left = @version.due_date.nil? ? "-" : get_workdays(today, @version.due_date) 46 | end 47 | 48 | # Users loop 49 | @user_id_array.each {|user_id| 50 | 51 | # Get all issues belongs to this version by user 52 | issues = Issue.where(assigned_to_id: user_id) 53 | .where(project_id: @project.id) 54 | .where(fixed_version_id: @version_id) 55 | .where(is_private: 0) 56 | 57 | # Reset counter 58 | earned_value_total = 0.0 59 | total_estimated_hours = 0.0 60 | total_spent_hours = 0.0 61 | 62 | # Earned value and other summary values 63 | issues.each {|issue| 64 | earned_value_total += (issue.estimated_hours || 0.0).to_f * (issue.done_ratio.to_f || 0) / 100.0 65 | total_estimated_hours += (issue.estimated_hours || 0.0).to_f 66 | total_spent_hours += (issue.spent_hours || 0.0).to_f 67 | } 68 | 69 | # Start and end date within the version 70 | real_start_date = issues.minimum('start_date') 71 | real_due_date = issues.maximum('due_date') 72 | 73 | # This is my unique definition for start date 74 | if real_start_date.nil? then 75 | real_start_date = today 76 | end 77 | 78 | # This is my unique definition for due date 79 | if real_due_date.nil? then 80 | nessesary_days = (total_estimated_hours / ChakuchiConstants::WORKING_HOURS_PER_DAY).ceil # Round up 81 | real_due_date = add_days_in_weekday(real_start_date, nessesary_days) 82 | end 83 | 84 | # Count workdays and calc. schedule progress rate (from 0.0@start_date to 1.0@due_date) 85 | if today <= real_start_date then 86 | schedule_progress_rate = 0.0 87 | elsif real_due_date <= today then 88 | schedule_progress_rate = 1.0 89 | elsif real_start_date < today && today < real_due_date then 90 | @workdays_total = get_workdays(real_start_date, real_due_date) 91 | @workdays_to_today = get_workdays(real_start_date, today) 92 | schedule_progress_rate = @workdays_to_today.to_f / @workdays_total.to_f 93 | else 94 | # can not in 95 | schedule_progress_rate = 0.0 96 | end 97 | 98 | # days_left > 0 is indicated schedule is delay 99 | estimated_hours_today = total_estimated_hours * schedule_progress_rate 100 | days_left = (estimated_hours_today - earned_value_total) / ChakuchiConstants::WORKING_HOURS_PER_DAY 101 | days_left = days_left.ceil # Round up 102 | 103 | # Estimated Completion Date 104 | if (total_estimated_hours - earned_value_total) <= 0 then 105 | # Already completed 106 | estimated_completion_date = "-" 107 | else 108 | if real_due_date.nil? then 109 | if real_start_date.nil? then 110 | estimated_completion_date = "-" 111 | else 112 | correct_days_left = days_left - (total_spent_hours / ChakuchiConstants::WORKING_HOURS_PER_DAY).ceil # Round up 113 | estimated_completion_date = add_days_in_weekday(real_start_date, correct_days_left).strftime("%Y-%m-%d") 114 | end 115 | else 116 | estimated_completion_date = add_days_in_weekday(real_due_date, days_left).strftime("%Y-%m-%d") 117 | end 118 | end 119 | 120 | # Store values for view 121 | @users_hash[user_id] = user_id.nil? ? nil : User.find(user_id) 122 | @number_of_issues_hash[user_id] = issues.nil? ? 0 : issues.length 123 | @start_date_hash[user_id] = real_start_date.nil? ? "-" : real_start_date.strftime("%Y-%m-%d") 124 | @due_date_hash[user_id] = real_due_date.nil? ? "-" : real_due_date.strftime("%Y-%m-%d") 125 | @total_estimated_hours_hash[user_id] = total_estimated_hours 126 | @estimated_hours_today_hash[user_id] = estimated_hours_today 127 | @total_spent_hours_hash[user_id] = total_spent_hours 128 | @earned_value_total_hash[user_id] = earned_value_total 129 | @days_late_hash[user_id] = days_left 130 | @total_due_date_hash[user_id] = real_due_date.nil? ? "-" : real_due_date.strftime("%Y-%m-%d") 131 | @estimated_completion_date_hash[user_id] = estimated_completion_date 132 | } # End of users loop 133 | 134 | end 135 | 136 | private 137 | # 138 | # Count workdays 139 | # 140 | def get_workdays(date_from, date_to) 141 | # Reverse 142 | if date_from > date_to then 143 | swap = date_from 144 | date_to = date_from 145 | date_to = swap 146 | end 147 | # Counter 148 | workdays = 0 149 | t = date_from 150 | while true do 151 | if t.saturday? || t.sunday? then 152 | # nothing to do 153 | else 154 | workdays += 1 155 | end 156 | t += 1.days 157 | if t.strftime("%Y-%m-%d") >= date_to.strftime("%Y-%m-%d") then 158 | break 159 | end 160 | end 161 | return workdays == 0 ? 1 : workdays 162 | end 163 | 164 | # 165 | # Get date before/after n days except sat. & sun. 166 | # 167 | def add_days_in_weekday(ref_date, diff) 168 | # on schedule 169 | if diff == 0 then 170 | return ref_date 171 | end 172 | count = 0 173 | t = ref_date 174 | # when progress 175 | if diff < 0 then 176 | while true do 177 | if count == diff then 178 | break 179 | end 180 | t -= 1.days 181 | if t.saturday? || t.sunday? then 182 | # nothing to do 183 | else 184 | count -= 1 185 | end 186 | end 187 | end 188 | # when delay 189 | if diff > 0 then 190 | while true do 191 | if count == diff then 192 | break 193 | end 194 | t += 1.days 195 | if t.saturday? || t.sunday? then 196 | # nothing to do 197 | else 198 | count += 1 199 | end 200 | end 201 | end 202 | return t 203 | end 204 | 205 | # 206 | # Discard session 207 | # 208 | def discard_session 209 | session[:chakuchi] = nil 210 | end 211 | 212 | # 213 | # Store session 214 | # 215 | def store_params_to_session 216 | session_hash = {} 217 | session_hash["project_id"] = @project_id 218 | session_hash["version_id"] = @version_id 219 | session[:chakuchi] = session_hash 220 | end 221 | 222 | # 223 | # Restore session 224 | # 225 | def restore_params_from_session 226 | session_hash = session[:chakuchi] 227 | 228 | # Display version ID 229 | if !session_hash.blank? && params[:version_id].blank? 230 | @version_id = session_hash["version_id"] 231 | else 232 | @version_id = params[:version_id] 233 | end 234 | 235 | # If chenge project then reset version 236 | if !session_hash.blank? && !params[:project_id].blank? 237 | if session_hash["project_id"] != params[:project_id] then 238 | @version_id = nil 239 | end 240 | end 241 | end 242 | 243 | # 244 | # Initialize params 245 | # 246 | def initialize_params 247 | @project_id = params[:project_id] 248 | 249 | # Get current project 250 | @project = Project.find(@project_id) 251 | 252 | # Display version ID 253 | if @version_id.nil? || @version_id == "" then 254 | # no selection 255 | @version_id = "" 256 | @version = nil 257 | else 258 | # selected version 259 | @version = Version.find(@version_id) 260 | end 261 | end 262 | 263 | # 264 | # User logged in 265 | # 266 | def set_user 267 | @current_user ||= User.current 268 | end 269 | 270 | # 271 | # Need Login 272 | # 273 | def global_authorize 274 | set_user 275 | render_403 unless @current_user.type == 'User' 276 | end 277 | 278 | end 279 | -------------------------------------------------------------------------------- /app/views/chakuchi/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%= javascript_include_tag "chakuchi", :plugin => "chakuchi" %> 3 | 4 | <%= stylesheet_link_tag 'chakuchi', :plugin => 'chakuchi' %> 5 |
表示するデータがありません
36 | <% end %> 37 | 38 | <% if @version != nil then %> 39 || <%= I18n.t(:chakuchi_label_version_name) %> | 45 |<%= I18n.t(:chakuchi_label_start_date) %> | 46 |<%= I18n.t(:chakuchi_label_due_date) %> | 47 |<%= I18n.t(:chakuchi_label_issues_count) %> | 48 |<%= I18n.t(:chakuchi_label_closed_issues_count) %> | 49 |<%= I18n.t(:chakuchi_label_workdays_left) %> | 50 |<%= I18n.t(:chakuchi_label_days_late) %> | 51 |<%= I18n.t(:chakuchi_label_estimated_completion_date) %> | 52 |
|---|---|---|---|---|---|---|---|
| <%= @version.name %> | 57 |<%= (@version.start_date || "-") %> | 58 |<%= (@version.due_date || "-") %> | 59 |<%= @version.issues_count %> | 60 |<%= @version.issues_count - @version.open_issues_count %> | 61 |<%= (@workdays_left || "-") %> <%= I18n.t(:chakuchi_label_unit_days) %> | 62 |<%= @days_late_hash.values.inject(:+) %> <%= I18n.t(:chakuchi_label_unit_days) %> | 63 |<%= @estimated_completion_date_hash.max{ |x, y| x[1] <=> y[1] }[1] %> | 64 |
| <%= I18n.t(:chakuchi_label_user_name) %> | 77 |<%= I18n.t(:chakuchi_label_issues_count) %> | 78 |<%= I18n.t(:chakuchi_label_estimated_hours) %> | 79 |<%= I18n.t(:chakuchi_label_spent_hours) %> | 80 |<%= I18n.t(:chakuchi_label_progress) %> | 81 |<%= I18n.t(:chakuchi_label_days_late) %> | 82 |<%= I18n.t(:chakuchi_label_due_date) %> | 83 |<%= I18n.t(:chakuchi_label_estimated_completion_date) %> | 84 |
|---|---|---|---|---|---|---|---|
| <%= avatar(@users_hash[user_id], :size => "16") %> <%= name %> | 94 |<%= @number_of_issues_hash[user_id] %> | 95 |<%= @total_estimated_hours_hash[user_id] %> | 96 |<%= @total_spent_hours_hash[user_id] %> | 97 | <% progress = @earned_value_total_hash[user_id] != 0 ? ((@earned_value_total_hash[user_id].to_f / @total_estimated_hours_hash[user_id].to_f) * 100).round : 0 %> 98 |<%= progress %> | 99 |100 | <% if @total_estimated_hours_hash[user_id].to_i != 0 then %> 101 | <% if user_id.nil? then %><% id_str = "user-dialog-0" %><% else %><% id_str = "user-dialog-" + user_id.to_s %><% end %> 102 | 103 | <%= @days_late_hash[user_id] %> <%= I18n.t(:chakuchi_label_unit_days) %> 104 | 105 | <% else %> 106 | <%= @days_late_hash[user_id] %> <%= I18n.t(:chakuchi_label_unit_days) %> 107 | <% end %> 108 | | 109 |<%= @number_of_issues_hash[user_id] == 0 ? "-" : @total_due_date_hash[user_id] %> | 110 |<%= @estimated_completion_date_hash[user_id] %> | 111 |
| <%= I18n.t(:chakuchi_label_total) %> | 115 |116 | | 117 | | 118 | | 119 | | 120 | | 121 | | 122 | |
| 125 | | <%= @version.issues_count %> | 126 |<%= @version.estimated_hours %> | 127 |<%= @version.spent_hours %> | 128 | <% earned_value_total = @earned_value_total_hash.values.inject(:+) %> 129 | <% progress_total = earned_value_total.to_i != 0 ? (earned_value_total.to_f / @version.estimated_hours.to_f * 100.to_f).round : 0 %> 130 |<%= progress_total %> | 131 |<%= @days_late_hash.values.inject(:+) %> <%= I18n.t(:chakuchi_label_unit_days) %> | 132 |- | 133 |<%= @estimated_completion_date_hash.max{ |x, y| x[1] <=> y[1] }[1] %> | 134 |