├── .gitignore ├── .hgignore ├── Gemfile.local ├── README.md ├── app ├── controllers │ └── rest_days_controller.rb ├── helpers │ └── rest_days_helper.rb ├── models │ ├── rest_day.rb │ ├── rest_day_csv.rb │ └── rest_day_range.rb └── views │ ├── gantts │ ├── 2.x │ │ └── show.html.erb │ ├── 3.x │ │ └── show.html.erb │ └── show.html.erb │ └── rest_days │ └── index.html.erb ├── assets └── stylesheets │ └── redmine_work_days.css ├── config ├── locales │ ├── en.yml │ └── ja.yml └── routes.rb ├── db └── migrate │ └── 001_create_rest_days.rb ├── init.rb ├── lib ├── redmine_utils_patch.rb └── redmine_work_days │ └── hooks.rb ├── spec ├── controllers │ └── rest_days_controller_spec.rb ├── factories │ ├── rest_day.rb │ └── user.rb ├── fixtures │ ├── rest_days.csv │ └── rest_days.txt ├── spec_helper.rb └── support │ └── controller_helpers.rb └── test ├── functional └── rest_days_controller_test.rb ├── test_data └── rest_days_jp_utf8_lf.csv ├── test_helper.rb └── unit ├── date_calculation_with_rest_day_test.rb └── rest_day_test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .hg 3 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | .git/ 4 | -------------------------------------------------------------------------------- /Gemfile.local: -------------------------------------------------------------------------------- 1 | group :test do 2 | gem 'rspec-rails' 3 | gem 'factory_girl_rails' 4 | gem 'database_cleaner' 5 | end 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redmine Work Days Plugin 2 | 3 | ## イントロダクション 4 | 5 | Redmineに個別の休業日の設定を追加するプラグインです。 6 | 7 | 休業日に設定された日は、Redmine標準の休業日同様に扱われ、ガントチャート上でグレー表示されます。 8 | 9 | ## 機能 10 | 11 | * 管理画面でCSV読み込ませて休業日を設定することができる 12 | * ガントチャート上での休業日を表示することができる 13 | 休業日を反映するためにはRedmineインスタンスの再起動が必要です。 14 | 15 | ## インストール方法 16 | 17 | 以下は `production` 環境でRedmineを動作させている前提です。 18 | 19 | ### Gitを使う 20 | 21 | ``` 22 | $ cd [Redmine Root] 23 | $ git clone git@github.com:agileware-jp/redmine_work_days.git plugins/redmine_work_days 24 | $ bundle install 25 | $ RAILS_ENV=production bundle exec rake redmine:plugins:migrate NAME=redmine_work_days 26 | ``` 27 | 28 | ### ZIPファイルを使う 29 | 30 | 1. [Download ZIP]を押下する 31 | 2. ZIPファイルを解凍する 32 | 3. ディレクトリ名を「redmine_work_days」に変更する 33 | 4. プラグインを配備する 34 | 以下に「redmine_work_days」を配備する。 35 | 36 | ``` 37 | [Redmine Root]/plugins 38 | ``` 39 | 40 | 5. プラグインをインストールする 41 | 42 | ``` 43 | $ bundle install 44 | $ RAILS_ENV=production bundle exec rake redmine:plugins:migrate NAME=redmine_work_days 45 | ``` 46 | 47 | ## アンインストール方法 48 | 49 | 以下は `production` 環境でRedmineを動作させている前提です。 50 | 51 | ``` 52 | $ cd [Redmine Root] 53 | $ RAILS_ENV=production bundle exec rake redmine:plugins:migrate NAME=redmine_work_days VERSION=0 54 | $ rm -rf plugins/redmine_work_days 55 | ``` 56 | 57 | ## About 58 | 59 | Copyright (c) 2015 [Agileware Inc.](http://agileware.jp) released under the MIT license 60 | -------------------------------------------------------------------------------- /app/controllers/rest_days_controller.rb: -------------------------------------------------------------------------------- 1 | class RestDaysController < ApplicationController 2 | before_filter :require_admin 3 | before_filter :get_year 4 | 5 | unloadable 6 | 7 | def index 8 | @rest_day = RestDay.new 9 | @rest_days = RestDay.in_year(@year).all 10 | 11 | @rest_day_csv = RestDayCsv.new 12 | @rest_day_range = RestDayRange.new 13 | end 14 | 15 | def create 16 | @rest_day = RestDay.new(params[:rest_day]) 17 | if @rest_day.save 18 | RestDay.clear! 19 | redirect_to rest_days_path, :notice => l(:notice_create_rest_day_success) 20 | else 21 | redirect_to rest_days_path, :alert => l(:alert_create_rest_day_failure) 22 | end 23 | end 24 | 25 | def import 26 | @rest_day_csv = RestDayCsv.new(params[:rest_day_csv]) 27 | 28 | if @rest_day_csv.valid? 29 | begin 30 | @rest_day_csv.import_from_csv! 31 | RestDay.clear! 32 | rescue => e 33 | flash[:error] = e.message 34 | redirect_to rest_days_path 35 | return 36 | end 37 | redirect_to rest_days_path, :notice => l(:notice_import_rest_days_success, :count => @count) 38 | else 39 | @rest_day = RestDay.new 40 | @rest_days = RestDay.in_year(@year).all 41 | @rest_day_range = RestDayRange.new 42 | render :action => "index" 43 | end 44 | end 45 | 46 | def range_delete 47 | @rest_day_range = RestDayRange.new(params[:rest_day_range]) 48 | 49 | if @rest_day_range.valid? 50 | @rest_days = RestDay.between(@rest_day_range.from, @rest_day_range.to) 51 | @count = @rest_days.count 52 | @rest_days.destroy_all 53 | RestDay.clear! 54 | redirect_to rest_days_path, :notice => l(:notice_delete_rest_days_success, :count => @count) 55 | else 56 | @rest_day = RestDay.new 57 | @rest_day_csv = RestDayCsv.new 58 | @rest_days = RestDay.in_year(@year).all 59 | render :action => "index" 60 | end 61 | end 62 | 63 | private 64 | def get_year 65 | if session[:year].present? and params[:year].blank? 66 | @year = session[:year] 67 | else 68 | @year = params[:year].present? ? Date.new(params[:year].to_i, 1, 1) : Date.today 69 | session[:year] = @year 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/helpers/rest_days_helper.rb: -------------------------------------------------------------------------------- 1 | module RestDaysHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/models/rest_day.rb: -------------------------------------------------------------------------------- 1 | class RestDay < ActiveRecord::Base 2 | unloadable 3 | 4 | attr_accessible :day, :description 5 | 6 | default_scope { order("#{RestDay.table_name}.day ASC") } 7 | 8 | scope :in_year, lambda{|year| where(:day => year.beginning_of_year..year.end_of_year)} 9 | scope :between, lambda{|day1, day2| where(:day => day1..day2)} 10 | 11 | validates_presence_of :day 12 | validates_length_of :description, :maximum => 200, :allow_nil => true 13 | 14 | class << self 15 | def rest_day?(date) 16 | date = Date.parse(date) unless date.is_a?(Date) 17 | !!rest_days.detect { |d| d.day == date } 18 | end 19 | 20 | def rest_days 21 | @rest_days ||= all 22 | end 23 | 24 | def clear! 25 | @rest_days = nil 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/models/rest_day_csv.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | require 'nkf' 3 | require 'csv' 4 | require 'mime/types' 5 | class RestDayCsv 6 | include ActiveModel::Validations 7 | include ActiveModel::Conversion 8 | extend ActiveModel::Naming 9 | 10 | DAY_COLUMN = 0 11 | DESCRIPTION_COLUMN = 1 12 | 13 | attr_accessor :file 14 | attr_accessor :headers, :data_rows 15 | 16 | validate :check_file_format 17 | validates :file, presence: true 18 | 19 | def initialize(attributes = {}) 20 | attributes.each do |name, value| 21 | send("#{name}=", value) 22 | end 23 | end 24 | 25 | def persisted? 26 | false 27 | end 28 | 29 | def import_from_csv! 30 | io_string = NKF.nkf('-w', self.file.read) 31 | 32 | line_num = 1 33 | ActiveRecord::Base.transaction do 34 | CSV.parse(io_string) do |row| 35 | RestDay.create!(:day => row[DAY_COLUMN], :description => row[DESCRIPTION_COLUMN]) 36 | line_num += 1 37 | end 38 | end 39 | 40 | rescue => e 41 | Rails.logger.warn("import_from_csv error.") 42 | Rails.logger.warn(e) 43 | raise "CSV読み込みエラー: #{line_num}行目でエラーが発生しました" 44 | end 45 | 46 | private 47 | def check_file_format 48 | errors.add(:file, :invalid) unless MIME::Types.type_for(self.file.original_filename).include?('text/csv') 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /app/models/rest_day_range.rb: -------------------------------------------------------------------------------- 1 | class RestDayRange 2 | include ActiveModel::Validations 3 | include ActiveModel::Conversion 4 | extend ActiveModel::Naming 5 | 6 | attr_accessor :from, :to, :from_string, :to_string 7 | 8 | #validates_presence_of :from, :to 9 | validate :check_inversion 10 | 11 | def initialize(attributes = {}) 12 | attributes.each do |name, value| 13 | begin 14 | send("#{name}_string=", value) 15 | send("#{name}=", Date.parse(value)) 16 | rescue => e 17 | Rails.logger.warn(e) 18 | end 19 | end 20 | end 21 | 22 | def persisted? 23 | false 24 | end 25 | 26 | private 27 | def check_inversion 28 | # format check 29 | errors.add(:from, :invalid) unless /\A\d{1,4}\-\d{1,2}\-\d{1,2}\Z/ =~ self.from_string 30 | errors.add(:to, :invalid) unless /\A\d{1,4}\-\d{1,2}\-\d{1,2}\Z/ =~ self.to_string 31 | 32 | if errors.blank? 33 | errors.add(:from, :greater_than, :to => RestDayRange.human_attribute_name(:to)) if self.from > self.to 34 | end 35 | end 36 | end -------------------------------------------------------------------------------- /app/views/gantts/2.x/show.html.erb: -------------------------------------------------------------------------------- 1 | <% @gantt.view = self %> 2 |
3 | <% if !@query.new_record? && @query.editable_by?(User.current) %> 4 | <%= link_to l(:button_edit), edit_query_path(@query, :gantt => 1), :class => 'icon icon-edit' %> 5 | <%= delete_link query_path(@query, :gantt => 1) %> 6 | <% end %> 7 |
8 | 9 |

<%= @query.new_record? ? l(:label_gantt) : h(@query.name) %>

10 | 11 | <%= form_tag({:controller => 'gantts', :action => 'show', 12 | :project_id => @project, :month => params[:month], 13 | :year => params[:year], :months => params[:months]}, 14 | :method => :get, :id => 'query_form') do %> 15 | <%= hidden_field_tag 'set_filter', '1' %> 16 | <%= hidden_field_tag 'gantt', '1' %> 17 |
"> 18 | <%= l(:label_filter_plural) %> 19 |
"> 20 | <%= render :partial => 'queries/filters', :locals => {:query => @query} %> 21 |
22 |
23 | 56 | 57 |

58 | <%= gantt_zoom_link(@gantt, :in) %> 59 | <%= gantt_zoom_link(@gantt, :out) %> 60 |

61 | 62 |

63 | <%= text_field_tag 'months', @gantt.months, :size => 2 %> 64 | <%= l(:label_months_from) %> 65 | <%= select_month(@gantt.month_from, :prefix => "month", :discard_type => true) %> 66 | <%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %> 67 | <%= hidden_field_tag 'zoom', @gantt.zoom %> 68 | 69 | <%= link_to_function l(:button_apply), '$("#query_form").submit()', 70 | :class => 'icon icon-checked' %> 71 | <%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, 72 | :class => 'icon icon-reload' %> 73 | <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> 74 | <%= link_to_function l(:button_save), 75 | "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit();", 76 | :class => 'icon icon-save' %> 77 | <% end %> 78 |

79 | <% end %> 80 | 81 | <%= error_messages_for 'query' %> 82 | <% if @query.valid? %> 83 | <% 84 | zoom = 1 85 | @gantt.zoom.times { zoom = zoom * 2 } 86 | 87 | subject_width = 330 88 | header_height = 18 89 | 90 | headers_height = header_height 91 | show_weeks = false 92 | show_days = false 93 | 94 | if @gantt.zoom > 1 95 | show_weeks = true 96 | headers_height = 2 * header_height 97 | if @gantt.zoom > 2 98 | show_days = true 99 | headers_height = 3 * header_height 100 | end 101 | end 102 | 103 | # Width of the entire chart 104 | g_width = ((@gantt.date_to - @gantt.date_from + 1) * zoom).to_i 105 | @gantt.render(:top => headers_height + 8, 106 | :zoom => zoom, 107 | :g_width => g_width, 108 | :subject_width => subject_width) 109 | g_height = [(20 * (@gantt.number_of_rows + 6)) + 150, 206].max 110 | t_height = g_height + headers_height 111 | %> 112 | 113 | <% if @gantt.truncated %> 114 |

<%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %>

115 | <% end %> 116 | 117 | 118 | 119 | 149 | 150 | 291 | 292 |
120 | <% 121 | style = "" 122 | style += "position:relative;" 123 | style += "height: #{t_height + 24}px;" 124 | style += "width: #{subject_width + 1}px;" 125 | %> 126 | <%= content_tag(:div, :style => style) do %> 127 | <% 128 | style = "" 129 | style += "right:-2px;" 130 | style += "width: #{subject_width}px;" 131 | style += "height: #{headers_height}px;" 132 | style += 'background: #eee;' 133 | %> 134 | <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> 135 | <% 136 | style = "" 137 | style += "right:-2px;" 138 | style += "width: #{subject_width}px;" 139 | style += "height: #{t_height}px;" 140 | style += 'border-left: 1px solid #c0c0c0;' 141 | style += 'overflow: hidden;' 142 | %> 143 | <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> 144 | <%= content_tag(:div, :class => "gantt_subjects") do %> 145 | <%= @gantt.subjects.html_safe %> 146 | <% end %> 147 | <% end %> 148 | 151 |
152 | <% 153 | style = "" 154 | style += "width: #{g_width - 1}px;" 155 | style += "height: #{headers_height}px;" 156 | style += 'background: #eee;' 157 | %> 158 | <%= content_tag(:div, ' '.html_safe, :style => style, :class => "gantt_hdr") %> 159 | 160 | <% ###### Months headers ###### %> 161 | <% 162 | month_f = @gantt.date_from 163 | left = 0 164 | height = (show_weeks ? header_height : header_height + g_height) 165 | %> 166 | <% @gantt.months.times do %> 167 | <% 168 | width = (((month_f >> 1) - month_f) * zoom - 1).to_i 169 | style = "" 170 | style += "left: #{left}px;" 171 | style += "width: #{width}px;" 172 | style += "height: #{height}px;" 173 | %> 174 | <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> 175 | <%= link_to h("#{month_f.year}-#{month_f.month}"), 176 | @gantt.params.merge(:year => month_f.year, :month => month_f.month), 177 | :title => "#{month_name(month_f.month)} #{month_f.year}" %> 178 | <% end %> 179 | <% 180 | left = left + width + 1 181 | month_f = month_f >> 1 182 | %> 183 | <% end %> 184 | 185 | <% ###### Weeks headers ###### %> 186 | <% if show_weeks %> 187 | <% 188 | left = 0 189 | height = (show_days ? header_height - 1 : header_height - 1 + g_height) 190 | %> 191 | <% if @gantt.date_from.cwday == 1 %> 192 | <% 193 | # @date_from is monday 194 | week_f = @gantt.date_from 195 | %> 196 | <% else %> 197 | <% 198 | # find next monday after @date_from 199 | week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1) 200 | width = (7 - @gantt.date_from.cwday + 1) * zoom - 1 201 | style = "" 202 | style += "left: #{left}px;" 203 | style += "top: 19px;" 204 | style += "width: #{width}px;" 205 | style += "height: #{height}px;" 206 | %> 207 | <%= content_tag(:div, ' '.html_safe, 208 | :style => style, :class => "gantt_hdr") %> 209 | <% left = left + width + 1 %> 210 | <% end %> 211 | <% while week_f <= @gantt.date_to %> 212 | <% 213 | width = ((week_f + 6 <= @gantt.date_to) ? 214 | 7 * zoom - 1 : 215 | (@gantt.date_to - week_f + 1) * zoom - 1).to_i 216 | style = "" 217 | style += "left: #{left}px;" 218 | style += "top: 19px;" 219 | style += "width: #{width}px;" 220 | style += "height: #{height}px;" 221 | %> 222 | <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> 223 | <%= content_tag(:small) do %> 224 | <%= week_f.cweek if width >= 16 %> 225 | <% end %> 226 | <% end %> 227 | <% 228 | left = left + width + 1 229 | week_f = week_f + 7 230 | %> 231 | <% end %> 232 | <% end %> 233 | 234 | <% ###### Days headers ####### %> 235 | <% if show_days %> 236 | <% 237 | left = 0 238 | height = g_height + header_height - 1 239 | wday = @gantt.date_from.cwday 240 | %> 241 | <% (@gantt.date_from .. @gantt.date_to).each do |g_date| %> 242 | <% 243 | width = zoom - 1 244 | style = "" 245 | style += "left: #{left}px;" 246 | style += "top:37px;" 247 | style += "width: #{width}px;" 248 | style += "height: #{height}px;" 249 | style += "font-size:0.7em;" 250 | clss = "gantt_hdr" 251 | clss << " nwday" if @gantt.non_working_day?(g_date) 252 | %> 253 | <%= content_tag(:div, :style => style, :class => clss) do %> 254 | <%= day_letter(wday) %> 255 | <% end %> 256 | <% 257 | left = left + width + 1 258 | wday = wday + 1 259 | wday = 1 if wday > 7 260 | %> 261 | <% end %> 262 | <% end %> 263 | 264 | <%= @gantt.lines.html_safe %> 265 | 266 | <% ###### Today red line (excluded from cache) ###### %> 267 | <% if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %> 268 | <% 269 | today_left = (((Date.today - @gantt.date_from + 1) * zoom).floor() - 1).to_i 270 | style = "" 271 | style += "position: absolute;" 272 | style += "height: #{g_height}px;" 273 | style += "top: #{headers_height + 1}px;" 274 | style += "left: #{today_left}px;" 275 | style += "width:10px;" 276 | style += "border-left: 1px dashed red;" 277 | %> 278 | <%= content_tag(:div, ' '.html_safe, :style => style, :id => 'today_line') %> 279 | <% end %> 280 | <% 281 | style = "" 282 | style += "position: absolute;" 283 | style += "height: #{g_height}px;" 284 | style += "top: #{headers_height + 1}px;" 285 | style += "left: 0px;" 286 | style += "width: #{g_width - 1}px;" 287 | %> 288 | <%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %> 289 |
290 |
293 | 294 | 295 | 296 | 300 | 304 | 305 |
297 | <%= link_to_content_update("\xc2\xab " + l(:label_previous), 298 | params.merge(@gantt.params_previous)) %> 299 | 301 | <%= link_to_content_update(l(:label_next) + " \xc2\xbb", 302 | params.merge(@gantt.params_next)) %> 303 |
306 | 307 | <% other_formats_links do |f| %> 308 | <%= f.link_to 'PDF', :url => params.merge(@gantt.params) %> 309 | <%= f.link_to('PNG', :url => params.merge(@gantt.params)) if @gantt.respond_to?('to_image') %> 310 | <% end %> 311 | <% end # query.valid? %> 312 | 313 | <% content_for :sidebar do %> 314 | <%= render :partial => 'issues/sidebar' %> 315 | <% end %> 316 | 317 | <% html_title(l(:label_gantt)) -%> 318 | 319 | <% content_for :header_tags do %> 320 | <%= javascript_include_tag 'raphael' %> 321 | <%= javascript_include_tag 'gantt' %> 322 | <% end %> 323 | 324 | <%= javascript_tag do %> 325 | var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; 326 | $(document).ready(drawGanttHandler); 327 | $(window).resize(drawGanttHandler); 328 | $(function() { 329 | $("#draw_relations").change(drawGanttHandler); 330 | $("#draw_progress_line").change(drawGanttHandler); 331 | }); 332 | <% end %> 333 | -------------------------------------------------------------------------------- /app/views/gantts/3.x/show.html.erb: -------------------------------------------------------------------------------- 1 | <% @gantt.view = self %> 2 |
3 | <% if !@query.new_record? && @query.editable_by?(User.current) %> 4 | <%= link_to l(:button_edit), edit_query_path(@query, :gantt => 1), :class => 'icon icon-edit' %> 5 | <%= delete_link query_path(@query, :gantt => 1) %> 6 | <% end %> 7 |
8 | 9 |

<%= @query.new_record? ? l(:label_gantt) : h(@query.name) %>

10 | 11 | <%= form_tag({:controller => 'gantts', :action => 'show', 12 | :project_id => @project, :month => params[:month], 13 | :year => params[:year], :months => params[:months]}, 14 | :method => :get, :id => 'query_form') do %> 15 | <%= hidden_field_tag 'set_filter', '1' %> 16 | <%= hidden_field_tag 'gantt', '1' %> 17 |
"> 18 | <%= l(:label_filter_plural) %> 19 |
"> 20 | <%= render :partial => 'queries/filters', :locals => {:query => @query} %> 21 |
22 |
23 | 56 | 57 |

58 | <%= gantt_zoom_link(@gantt, :in) %> 59 | <%= gantt_zoom_link(@gantt, :out) %> 60 |

61 | 62 |

63 | <%= text_field_tag 'months', @gantt.months, :size => 2 %> 64 | <%= l(:label_months_from) %> 65 | <%= select_month(@gantt.month_from, :prefix => "month", :discard_type => true) %> 66 | <%= select_year(@gantt.year_from, :prefix => "year", :discard_type => true) %> 67 | <%= hidden_field_tag 'zoom', @gantt.zoom %> 68 | 69 | <%= link_to_function l(:button_apply), '$("#query_form").submit()', 70 | :class => 'icon icon-checked' %> 71 | <%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 }, 72 | :class => 'icon icon-reload' %> 73 | <% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %> 74 | <%= link_to_function l(:button_save), 75 | "$('#query_form').attr('action', '#{ @project ? new_project_query_path(@project) : new_query_path }').submit();", 76 | :class => 'icon icon-save' %> 77 | <% end %> 78 |

79 | <% end %> 80 | 81 | <%= error_messages_for 'query' %> 82 | <% if @query.valid? %> 83 | <% 84 | zoom = 1 85 | @gantt.zoom.times { zoom = zoom * 2 } 86 | 87 | subject_width = 330 88 | header_height = 18 89 | 90 | headers_height = header_height 91 | show_weeks = false 92 | show_days = false 93 | show_day_num = false 94 | 95 | if @gantt.zoom > 1 96 | show_weeks = true 97 | headers_height = 2 * header_height 98 | if @gantt.zoom > 2 99 | show_days = true 100 | headers_height = 3 * header_height 101 | if @gantt.zoom > 3 102 | show_day_num = true 103 | headers_height = 4 * header_height 104 | end 105 | end 106 | end 107 | 108 | # Width of the entire chart 109 | g_width = ((@gantt.date_to - @gantt.date_from + 1) * zoom).to_i 110 | @gantt.render(:top => headers_height + 8, 111 | :zoom => zoom, 112 | :g_width => g_width, 113 | :subject_width => subject_width) 114 | g_height = [(20 * (@gantt.number_of_rows + 6)) + 150, 206].max 115 | t_height = g_height + headers_height 116 | %> 117 | 118 | <% if @gantt.truncated %> 119 |

<%= l(:notice_gantt_chart_truncated, :max => @gantt.max_rows) %>

120 | <% end %> 121 | 122 | 123 | 124 | 154 | 155 | 330 | 331 |
125 | <% 126 | style = "" 127 | style += "position:relative;" 128 | style += "height: #{t_height + 24}px;" 129 | style += "width: #{subject_width + 1}px;" 130 | %> 131 | <%= content_tag(:div, :style => style) do %> 132 | <% 133 | style = "" 134 | style += "right:-2px;" 135 | style += "width: #{subject_width}px;" 136 | style += "height: #{headers_height}px;" 137 | style += 'background: #eee;' 138 | %> 139 | <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> 140 | <% 141 | style = "" 142 | style += "right:-2px;" 143 | style += "width: #{subject_width}px;" 144 | style += "height: #{t_height}px;" 145 | style += 'border-left: 1px solid #c0c0c0;' 146 | style += 'overflow: hidden;' 147 | %> 148 | <%= content_tag(:div, "", :style => style, :class => "gantt_hdr") %> 149 | <%= content_tag(:div, :class => "gantt_subjects") do %> 150 | <%= @gantt.subjects.html_safe %> 151 | <% end %> 152 | <% end %> 153 | 156 |
157 | <% 158 | style = "" 159 | style += "width: #{g_width - 1}px;" 160 | style += "height: #{headers_height}px;" 161 | style += 'background: #eee;' 162 | %> 163 | <%= content_tag(:div, ' '.html_safe, :style => style, :class => "gantt_hdr") %> 164 | 165 | <% ###### Months headers ###### %> 166 | <% 167 | month_f = @gantt.date_from 168 | left = 0 169 | height = (show_weeks ? header_height : header_height + g_height) 170 | %> 171 | <% @gantt.months.times do %> 172 | <% 173 | width = (((month_f >> 1) - month_f) * zoom - 1).to_i 174 | style = "" 175 | style += "left: #{left}px;" 176 | style += "width: #{width}px;" 177 | style += "height: #{height}px;" 178 | %> 179 | <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> 180 | <%= link_to h("#{month_f.year}-#{month_f.month}"), 181 | @gantt.params.merge(:year => month_f.year, :month => month_f.month), 182 | :title => "#{month_name(month_f.month)} #{month_f.year}" %> 183 | <% end %> 184 | <% 185 | left = left + width + 1 186 | month_f = month_f >> 1 187 | %> 188 | <% end %> 189 | 190 | <% ###### Weeks headers ###### %> 191 | <% if show_weeks %> 192 | <% 193 | left = 0 194 | height = (show_days ? header_height - 1 : header_height - 1 + g_height) 195 | %> 196 | <% if @gantt.date_from.cwday == 1 %> 197 | <% 198 | # @date_from is monday 199 | week_f = @gantt.date_from 200 | %> 201 | <% else %> 202 | <% 203 | # find next monday after @date_from 204 | week_f = @gantt.date_from + (7 - @gantt.date_from.cwday + 1) 205 | width = (7 - @gantt.date_from.cwday + 1) * zoom - 1 206 | style = "" 207 | style += "left: #{left}px;" 208 | style += "top: 19px;" 209 | style += "width: #{width}px;" 210 | style += "height: #{height}px;" 211 | %> 212 | <%= content_tag(:div, ' '.html_safe, 213 | :style => style, :class => "gantt_hdr") %> 214 | <% left = left + width + 1 %> 215 | <% end %> 216 | <% while week_f <= @gantt.date_to %> 217 | <% 218 | width = ((week_f + 6 <= @gantt.date_to) ? 219 | 7 * zoom - 1 : 220 | (@gantt.date_to - week_f + 1) * zoom - 1).to_i 221 | style = "" 222 | style += "left: #{left}px;" 223 | style += "top: 19px;" 224 | style += "width: #{width}px;" 225 | style += "height: #{height}px;" 226 | %> 227 | <%= content_tag(:div, :style => style, :class => "gantt_hdr") do %> 228 | <%= content_tag(:small) do %> 229 | <%= week_f.cweek if width >= 16 %> 230 | <% end %> 231 | <% end %> 232 | <% 233 | left = left + width + 1 234 | week_f = week_f + 7 235 | %> 236 | <% end %> 237 | <% end %> 238 | 239 | <% ###### Day numbers headers ###### %> 240 | <% if show_day_num %> 241 | <% 242 | left = 0 243 | height = g_height + header_height*2 - 1 244 | wday = @gantt.date_from.cwday 245 | day_num = @gantt.date_from 246 | %> 247 | <% (@gantt.date_from .. @gantt.date_to).each do |g_date| %> 248 | <% 249 | width = zoom - 1 250 | style = "" 251 | style += "left:#{left}px;" 252 | style += "top:37px;" 253 | style += "width:#{width}px;" 254 | style += "height:#{height}px;" 255 | style += "font-size:0.7em;" 256 | clss = "gantt_hdr" 257 | clss << " nwday" if @gantt.non_working_week_days.include?(wday) || 258 | @gantt.non_working_day?(g_date) 259 | %> 260 | <%= content_tag(:div, :style => style, :class => clss) do %> 261 | <%= day_num.day %> 262 | <% end %> 263 | <% 264 | left = left + width+1 265 | day_num = day_num + 1 266 | wday = wday + 1 267 | wday = 1 if wday > 7 268 | %> 269 | <% end %> 270 | <% end %> 271 | 272 | <% ###### Days headers ####### %> 273 | <% if show_days %> 274 | <% 275 | left = 0 276 | height = g_height + header_height - 1 277 | top = (show_day_num ? 55 : 37) 278 | wday = @gantt.date_from.cwday 279 | %> 280 | <% (@gantt.date_from .. @gantt.date_to).each do |g_date| %> 281 | <% 282 | width = zoom - 1 283 | style = "" 284 | style += "left: #{left}px;" 285 | style += "top: #{top}px;" 286 | style += "width: #{width}px;" 287 | style += "height: #{height}px;" 288 | style += "font-size:0.7em;" 289 | clss = "gantt_hdr" 290 | clss << " nwday" if @gantt.non_working_day?(g_date) 291 | %> 292 | <%= content_tag(:div, :style => style, :class => clss) do %> 293 | <%= day_letter(wday) %> 294 | <% end %> 295 | <% 296 | left = left + width + 1 297 | wday = wday + 1 298 | wday = 1 if wday > 7 299 | %> 300 | <% end %> 301 | <% end %> 302 | 303 | <%= @gantt.lines.html_safe %> 304 | 305 | <% ###### Today red line (excluded from cache) ###### %> 306 | <% if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %> 307 | <% 308 | today_left = (((Date.today - @gantt.date_from + 1) * zoom).floor() - 1).to_i 309 | style = "" 310 | style += "position: absolute;" 311 | style += "height: #{g_height}px;" 312 | style += "top: #{headers_height + 1}px;" 313 | style += "left: #{today_left}px;" 314 | style += "width:10px;" 315 | style += "border-left: 1px dashed red;" 316 | %> 317 | <%= content_tag(:div, ' '.html_safe, :style => style, :id => 'today_line') %> 318 | <% end %> 319 | <% 320 | style = "" 321 | style += "position: absolute;" 322 | style += "height: #{g_height}px;" 323 | style += "top: #{headers_height + 1}px;" 324 | style += "left: 0px;" 325 | style += "width: #{g_width - 1}px;" 326 | %> 327 | <%= content_tag(:div, '', :style => style, :id => "gantt_draw_area") %> 328 |
329 |
332 | 333 | 334 | 335 | 340 | 345 | 346 |
336 | <%= link_to("\xc2\xab " + l(:label_previous), 337 | {:params => params.merge(@gantt.params_previous)}, 338 | :accesskey => accesskey(:previous)) %> 339 | 341 | <%= link_to(l(:label_next) + " \xc2\xbb", 342 | {:params => params.merge(@gantt.params_next)}, 343 | :accesskey => accesskey(:next)) %> 344 |
347 | 348 | <% other_formats_links do |f| %> 349 | <%= f.link_to 'PDF', :url => params.merge(@gantt.params) %> 350 | <%= f.link_to('PNG', :url => params.merge(@gantt.params)) if @gantt.respond_to?('to_image') %> 351 | <% end %> 352 | <% end # query.valid? %> 353 | 354 | <% content_for :sidebar do %> 355 | <%= render :partial => 'issues/sidebar' %> 356 | <% end %> 357 | 358 | <% html_title(l(:label_gantt)) -%> 359 | 360 | <% content_for :header_tags do %> 361 | <%= javascript_include_tag 'raphael' %> 362 | <%= javascript_include_tag 'gantt' %> 363 | <% end %> 364 | 365 | <%= javascript_tag do %> 366 | var issue_relation_type = <%= raw Redmine::Helpers::Gantt::DRAW_TYPES.to_json %>; 367 | $(document).ready(drawGanttHandler); 368 | $(window).resize(drawGanttHandler); 369 | $(function() { 370 | $("#draw_relations").change(drawGanttHandler); 371 | $("#draw_progress_line").change(drawGanttHandler); 372 | }); 373 | <% end %> 374 | -------------------------------------------------------------------------------- /app/views/gantts/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "gantts/#{Redmine::VERSION::MAJOR}.x/show" %> -------------------------------------------------------------------------------- /app/views/rest_days/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%=l(:label_rest_days)%>

2 | 3 | <%= error_messages_for @rest_day_csv %> 4 | <%= error_messages_for @rest_day_range %> 5 | 6 |

<%=l(:label_import_csv)%>

7 |
8 | <%= form_for @rest_day_csv, :multipart => true, :url => import_rest_days_path do |f| %> 9 | <%= f.file_field :file, :required => true %> 10 | <%= submit_tag l(:label_import_csv), :id => "create" %> 11 | <%- end -%> 12 | 13 |
14 |

読み込みCSVのフォーマット(例)

15 |
16 | 2013-04-29,昭和の日
17 | 2013-05-03,憲法記念日
18 | 2013-05-04,みどりの日
19 | 2013-05-05,こどもの日 20 |
21 |
22 |
23 | 24 |

<%=l(:label_create_rest_day)%>

25 |
26 | <%= form_for @rest_day, url: rest_days_path do |f| %> 27 |
28 | <%= l(:label_create_rest_day_day) %> 29 | <%= f.date_field :day, :size => 10, :required => true %><%= calendar_for('rest_day_day') %> 30 | <%= l(:label_create_rest_day_description) %> 31 | <%= f.text_field :description, :size => 20, :required => true %> 32 |
33 | 34 | <%= submit_tag l(:submit_create_rest_day) %> 35 | <%- end -%> 36 |
37 | 38 |

<%=l(:label_delete_rest_days)%>

39 |
40 | <%= form_for @rest_day_range, :multipart => true, :url => range_delete_rest_days_path, :method => :delete do |f| %> 41 |
42 | <%= f.date_field :from, :size => 10, :required => true %><%= calendar_for('rest_day_range_from') %> 43 | <%= l(:label_delete_rest_days_separator) %> 44 | <%= f.date_field :to, :size => 10, :required => true %><%= calendar_for('rest_day_range_to') %> 45 | <%= l(:label_delete_rest_days_description) %> 46 |
47 | 48 | <%= submit_tag l(:submit_delete_rest_days), :id => "delete" %> 49 | <%- end -%> 50 |
51 | 52 |

<%=l(:label_rest_days)%> - <%= @year.year -%>

53 | 54 |
55 |
56 | <%= link_to l(:label_prev_year), rest_days_path(:year => (@year - 1.years).year) %> |  57 |
58 |
59 | <%= link_to l(:label_current_year), rest_days_path(:year => Date.today.year) %> |  60 |
61 |
62 | <%= link_to l(:label_next_year), rest_days_path(:year => (@year + 1.years).year) %> 63 |
64 |
65 |
66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | <%- @rest_days.each do |rest_day| -%> 78 | 79 | 80 | 81 | 82 | 83 | <% end -%> 84 | 85 |
#<%= h(l(:field_rest_day_day)) %><%= h(l(:field_rest_day_description)) %>
<%= rest_day.id %><%= rest_day.day %><%= rest_day.description %>
86 |
87 | -------------------------------------------------------------------------------- /assets/stylesheets/redmine_work_days.css: -------------------------------------------------------------------------------- 1 | .icon-redmine-work-days { 2 | background-image: url('../../../images/calendar.png') 3 | } 4 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # English strings go here for Rails i18n 2 | en: 3 | rest_day: "Rest Day" 4 | rest_days: "Rest Days" 5 | 6 | label_rest_day: "Rest Day" 7 | label_rest_days: "Rest Days" 8 | label_rest_day_day: "Day" 9 | label_rest_day_description: "Date" 10 | 11 | field_rest_day_day: "Date" 12 | field_rest_day_description: "Description" 13 | 14 | # import 15 | label_import_csv: "Import from CSV" 16 | notice_import_rest_days_success: "Import successful finished." 17 | 18 | # create 19 | label_create_rest_day: "Add Rest Days" 20 | label_create_rest_day_day: "Day:" 21 | label_create_rest_day_description: "Date:" 22 | submit_create_rest_day: "Add Rest Days" 23 | notice_create_rest_day_success: "Success Rest Days Added" 24 | alert_create_rest_day_failure: "Failure Rest Days Added" 25 | 26 | # delete 27 | label_delete_rest_days_separator: "-" 28 | label_delete_rest_days_description: "" 29 | label_delete_rest_days: "Delete Rest Days" 30 | submit_delete_rest_days: "Delete Rest Days" 31 | notice_delete_rest_days_success: "Success %{count} Rest Days Deleted." 32 | 33 | # year select 34 | label_prev_year: "<< Prev Year" 35 | label_current_year: "Current Year" 36 | label_next_year: "Next Year >>" 37 | 38 | activemodel: 39 | attributes: 40 | rest_day_range: 41 | from: "From Date" 42 | to: "To Date" 43 | errors: 44 | models: 45 | rest_day_range: 46 | attributes: 47 | from: 48 | greater_than: "Please set date before %{count}" 49 | -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | # English strings go here for Rails i18n 2 | ja: 3 | rest_day: "会社休日設定" 4 | rest_days: "会社休日設定" 5 | 6 | label_rest_day: "会社休日" 7 | label_rest_days: "会社休日一覧" 8 | label_rest_day_day: "日付" 9 | label_rest_day_description: "説明" 10 | 11 | field_rest_day_day: "日付" 12 | field_rest_day_description: "説明" 13 | 14 | # import 15 | label_import_csv: "CSVから読み込む" 16 | notice_import_rest_days_success: "CSV読み込みが成功しました。" 17 | 18 | # create 19 | label_create_rest_day: "会社休日追加" 20 | label_create_rest_day_day: "日付:" 21 | label_create_rest_day_description: "説明:" 22 | submit_create_rest_day: "会社休日を追加" 23 | notice_create_rest_day_success: "休日設定を追加しました。" 24 | alert_create_rest_day_failure: "休日設定の追加に失敗しました。" 25 | 26 | # delete 27 | label_delete_rest_days_separator: "から" 28 | label_delete_rest_days_description: "までの会社休日設定を削除します。" 29 | label_delete_rest_days: "会社休日削除" 30 | submit_delete_rest_days: "会社休日を削除" 31 | notice_delete_rest_days_success: "%{count}件のデータが削除されました。" 32 | 33 | # year select 34 | label_prev_year: "《前" 35 | label_current_year: "今年" 36 | label_next_year: "次》" 37 | 38 | activemodel: 39 | attributes: 40 | rest_day_csv: 41 | file: "ファイル" 42 | rest_day_range: 43 | from: "開始日" 44 | to: "終了日" 45 | errors: 46 | models: 47 | rest_day_csv: 48 | attributes: 49 | file: 50 | invalid: "が不正です。" 51 | rest_day_range: 52 | attributes: 53 | to: 54 | invalid: "が不正です。" 55 | from: 56 | invalid: "が不正です。" 57 | greater_than: "は %{to} よりも前に設定してください。" 58 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # Plugin's routes 2 | # See: http://guides.rubyonrails.org/routing.html 3 | 4 | resources :rest_days do 5 | post :import, :on => :collection 6 | delete :range_delete, :on => :collection 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/001_create_rest_days.rb: -------------------------------------------------------------------------------- 1 | class CreateRestDays < ActiveRecord::Migration 2 | def change 3 | create_table :rest_days do |t| 4 | t.date :day 5 | t.string :description 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | require 'redmine' 4 | require 'redmine_utils_patch' 5 | require 'redmine_work_days/hooks' 6 | 7 | ActionDispatch::Callbacks.to_prepare do 8 | require_dependency 'redmine/utils' 9 | 10 | unless Redmine::Utils::DateCalculation.included_modules.include?(RedmineUtilsDateCalculationPatch) 11 | Redmine::Utils::DateCalculation.send :include, RedmineUtilsDateCalculationPatch 12 | end 13 | end 14 | 15 | Redmine::Plugin.register :redmine_work_days do 16 | name 'Redmine Work Days' 17 | author 'Agileware Inc.' 18 | description 'This plugin enables to set the holidays.' 19 | version '1.3.0' 20 | author_url 'http://agileware.jp' 21 | 22 | menu :admin_menu, :rest_days, 23 | { :controller => 'rest_days', :action => 'index' }, 24 | :caption => :rest_days, 25 | :html => { :class => 'icon icon-redmine-work-days'} 26 | end 27 | -------------------------------------------------------------------------------- /lib/redmine_utils_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'redmine/utils' 2 | 3 | # Patch for Redmine::Utils::DateCalculation 4 | module RedmineUtilsDateCalculationPatch 5 | 6 | def self.included(base) 7 | base.send(:include, InstanceMethods) 8 | base.class_eval do 9 | unloadable 10 | alias_method_chain :working_days, :rest_day 11 | alias_method_chain :add_working_days, :rest_day 12 | alias_method_chain :next_working_date, :rest_day 13 | alias_method_chain :non_working_week_days, :rest_day 14 | alias :non_working_week_day? :non_working_week_day? # FIXME check why this solves NoMethodError 15 | alias :non_working_day? :non_working_day? # FIXME check why this solves NoMethodError 16 | end 17 | end 18 | 19 | module InstanceMethods 20 | # Returns the number of working days between from and to 21 | def working_days_with_rest_day(from, to) 22 | (from...to).inject(0) do |days, date| 23 | if non_working_day?(date) 24 | days 25 | else 26 | days + 1 27 | end 28 | end 29 | end 30 | 31 | # Adds working days to the given date 32 | def add_working_days_with_rest_day(from, working_days) 33 | if working_days > 0 34 | result = from + working_days 35 | while working_days > working_days(from, result) 36 | result = next_working_date(result + 1) 37 | end 38 | next_working_date(result) 39 | else 40 | from 41 | end 42 | end 43 | 44 | # Returns the date of the first day on or after the given date that is a working day 45 | def next_working_date_with_rest_day(date) 46 | next_working_date = date 47 | while non_working_day?(next_working_date) 48 | next_working_date += 1 49 | end 50 | next_working_date 51 | end 52 | 53 | # Returns the index of non working week days (1=monday, 7=sunday) 54 | def non_working_week_days_with_rest_day 55 | @non_working_week_days ||= begin 56 | days = Setting.non_working_week_days 57 | if days.is_a?(Array) && days.size < 7 58 | days.map(&:to_i) 59 | else 60 | [] 61 | end 62 | end 63 | end 64 | 65 | # Checks if a date is non working week day 66 | def non_working_week_day?(date) 67 | non_working_week_days.include?(((date.cwday - 1) % 7) + 1) 68 | end 69 | 70 | # Checks if a date is non working day 71 | def non_working_day?(date) 72 | non_working_week_day?(date) || RestDay.rest_day?(date) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/redmine_work_days/hooks.rb: -------------------------------------------------------------------------------- 1 | module RedmineWorkDays 2 | class Hooks < Redmine::Hook::ViewListener 3 | def view_layouts_base_html_head(*) 4 | stylesheet_link_tag 'redmine_work_days', plugin: 'redmine_work_days' 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/rest_days_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../spec_helper', __FILE__) 2 | 3 | describe RestDaysController, type: :controller do 4 | describe 'POST create' do 5 | before { login_user } 6 | 7 | context '登録が正常終了する場合' do 8 | let(:params){ {rest_day: { day: '2015-05-13', description: 'testdata' }} } 9 | subject { post :create, params } 10 | 11 | it 'RestDayが1増加' do 12 | subject 13 | expect(RestDay.count).to eq(1) 14 | end 15 | end 16 | end 17 | 18 | describe 'POST import' do 19 | before { login_user } 20 | 21 | context 'CSVインポートが成功する場合' do 22 | let(:import_file) { File.expand_path('../../fixtures/rest_days.csv', __FILE__) } 23 | let(:params) { { rest_day_csv: { file: fixture_file_upload(import_file) } } } 24 | subject { post :import, params } 25 | 26 | it { expect { subject }.to(change { RestDay.count }.by(4)) } 27 | end 28 | 29 | context 'CSVインポートが失敗する場合' do 30 | context '入力ファイルの拡張子がcsv以外の場合' do 31 | render_views 32 | 33 | let(:import_file) { File.expand_path('../../fixtures/rest_days.txt', __FILE__) } 34 | let(:params) { { rest_day_csv: { file: fixture_file_upload(import_file) } } } 35 | subject { post :import, params } 36 | 37 | it { expect { subject }.to_not(change { RestDay.count }) } 38 | 39 | it 'フラッシュメッセージを含んだindex画面を表示する' do 40 | subject 41 | expect(response).to render_template(:index) 42 | expect(response).to be_success 43 | expect(response.body).to have_selector '#errorExplanation' 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/factories/rest_day.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :rest_day_right, class: RestDay do 3 | day Date.parse('2015-05-13') 4 | description 'testdata' 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | login "admin" 4 | hashed_password "9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b" 5 | firstname "Redmine" 6 | lastname "Admin" 7 | admin true 8 | status 1 9 | last_login_on Time.zone.now 10 | language "ja" 11 | auth_source_id 1 12 | created_on Time.zone.now 13 | updated_on Time.zone.now 14 | type "User" 15 | identity_url "http://" 16 | mail_notification "all" 17 | salt "3126f764c3c5ac61cbfc103f25f934cf" 18 | must_change_passwd false 19 | passwd_changed_on Time.zone.now 20 | mail "test@test.com" 21 | end 22 | end -------------------------------------------------------------------------------- /spec/fixtures/rest_days.csv: -------------------------------------------------------------------------------- 1 | 2013-04-29,昭和の日 2 | 2013-05-03,憲法記念日 3 | 2013-05-04,みどりの日 4 | 2013-05-05,こどもの日 5 | -------------------------------------------------------------------------------- /spec/fixtures/rest_days.txt: -------------------------------------------------------------------------------- 1 | 2013-04-29,昭和の日 2 | 2013-05-03,憲法記念日 3 | 2013-05-04,みどりの日 4 | 2013-05-05,こどもの日 5 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= 'test' 2 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 3 | require File.expand_path("../../../../config/environment", __FILE__) 4 | require 'rspec/rails' 5 | 6 | Dir[File.expand_path(File.dirname(__FILE__) + '/support/*.rb')].each { |f| require f } 7 | 8 | RSpec.configure do |config| 9 | config.use_transactional_fixtures = true 10 | config.infer_spec_type_from_file_location! 11 | config.include FactoryGirl::Syntax::Methods 12 | FactoryGirl.definition_file_paths = [File.expand_path("../factories", __FILE__)] 13 | config.include ControllerHelpers, type: :controller 14 | config.before(:all) do 15 | FactoryGirl.reload 16 | end 17 | 18 | config.before(:suite) do 19 | DatabaseCleaner.strategy = :truncation 20 | end 21 | 22 | config.before(:each) do 23 | DatabaseCleaner.start 24 | end 25 | 26 | config.after(:each) do 27 | DatabaseCleaner.clean 28 | end 29 | end -------------------------------------------------------------------------------- /spec/support/controller_helpers.rb: -------------------------------------------------------------------------------- 1 | module ControllerHelpers 2 | def login_user(user = create(:user)) 3 | session[:user_id] = user.id 4 | session[:ctime] = Time.now.tomorrow.utc.to_i 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /test/functional/rest_days_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class RestDaysControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | def test_truth 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_data/rest_days_jp_utf8_lf.csv: -------------------------------------------------------------------------------- 1 | 日付,説明 2 | 2012-04-14,楽しい祝日 3 | 2013-01-01,元日 4 | 2013-01-13,子供の誕生日 5 | 2013-04-1, エイプリルフール -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Load the Redmine helper 2 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 3 | -------------------------------------------------------------------------------- /test/unit/date_calculation_with_rest_day_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class RedmineUtilsDateCalculationWithRestDayTest < ActiveSupport::TestCase 4 | include Redmine::Utils::DateCalculation 5 | 6 | def test_working_days_without_non_working_week_days 7 | with_settings :non_working_week_days => [] do 8 | RestDay.create!(:day => Date.parse("2012-10-10"), :description => "Health Sports Day") 9 | RestDay.create!(:day => Date.parse("2012-10-16"), :description => "Dummy 1") 10 | RestDay.create!(:day => Date.parse("2012-10-30"), :description => "Dummy 2") 11 | RestDay.clear! 12 | 13 | # 2012-10-09 Tue 14 | assert_equal 18-2, working_days('2012-10-09'.to_date, '2012-10-27'.to_date) 15 | assert_equal 6-1, working_days('2012-10-09'.to_date, '2012-10-15'.to_date) 16 | assert_equal 5-1, working_days('2012-10-09'.to_date, '2012-10-14'.to_date) 17 | assert_equal 3-1, working_days('2012-10-09'.to_date, '2012-10-12'.to_date) 18 | assert_equal 3-1, working_days('2012-10-14'.to_date, '2012-10-17'.to_date) 19 | assert_equal 16-1, working_days('2012-10-14'.to_date, '2012-10-30'.to_date) 20 | end 21 | end 22 | 23 | def test_working_days_with_non_working_week_days 24 | with_settings :non_working_week_days => %w(6 7) do 25 | RestDay.create!(:day => Date.parse("2012-10-10"), :description => "Health Sports Day") 26 | RestDay.create!(:day => Date.parse("2012-10-16"), :description => "Dummy 1") 27 | RestDay.create!(:day => Date.parse("2012-10-30"), :description => "Dummy 2") 28 | RestDay.clear! 29 | 30 | # 2012-10-09 Tue 31 | assert_equal 14-2, working_days('2012-10-09'.to_date, '2012-10-27'.to_date) 32 | assert_equal 4-1, working_days('2012-10-09'.to_date, '2012-10-15'.to_date) 33 | assert_equal 4-1, working_days('2012-10-09'.to_date, '2012-10-14'.to_date) 34 | assert_equal 3-1, working_days('2012-10-09'.to_date, '2012-10-12'.to_date) 35 | assert_equal 8-2, working_days('2012-10-09'.to_date, '2012-10-19'.to_date) 36 | assert_equal 8-1, working_days('2012-10-11'.to_date, '2012-10-23'.to_date) 37 | assert_equal 2-1, working_days('2012-10-14'.to_date, '2012-10-17'.to_date) 38 | assert_equal 11-1, working_days('2012-10-14'.to_date, '2012-10-30'.to_date) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/unit/rest_day_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class RestDayTest < ActiveSupport::TestCase 4 | 5 | def test_rest_day? 6 | assert_equal false, RestDay.rest_day?(Date.parse("2013-01-21")) 7 | assert_equal false, RestDay.rest_day?(Date.parse("2013-01-22")) 8 | assert_equal false, RestDay.rest_day?(Date.parse("2013-01-23")) 9 | assert_equal false, RestDay.rest_day?(Date.parse("2013-01-24")) 10 | assert_equal false, RestDay.rest_day?(Date.parse("2013-01-25")) 11 | 12 | RestDay.create!(:day => Date.parse("2013-01-21"), :description => "Mon") 13 | RestDay.create!(:day => Date.parse("2013-01-22"), :description => "Tue") 14 | RestDay.create!(:day => Date.parse("2013-01-23"), :description => "Wed") 15 | RestDay.create!(:day => Date.parse("2013-01-24"), :description => "Thu") 16 | RestDay.create!(:day => Date.parse("2013-01-25"), :description => "Fri") 17 | RestDay.clear! 18 | 19 | assert_equal true, RestDay.rest_day?(Date.parse("2013-01-21")) 20 | assert_equal true, RestDay.rest_day?(Date.parse("2013-01-22")) 21 | assert_equal true, RestDay.rest_day?(Date.parse("2013-01-23")) 22 | assert_equal true, RestDay.rest_day?(Date.parse("2013-01-24")) 23 | assert_equal true, RestDay.rest_day?(Date.parse("2013-01-25")) 24 | end 25 | end 26 | --------------------------------------------------------------------------------