├── .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 |
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 |
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 | |
149 |
150 |
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 | |
291 |
292 |
293 |
294 |
295 |
296 |
297 | <%= link_to_content_update("\xc2\xab " + l(:label_previous),
298 | params.merge(@gantt.params_previous)) %>
299 | |
300 |
301 | <%= link_to_content_update(l(:label_next) + " \xc2\xbb",
302 | params.merge(@gantt.params_next)) %>
303 | |
304 |
305 |
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 |
19 | ">
20 | <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
41 |
42 | |
43 |
44 |
45 |
46 |
50 |
51 | |
52 |
53 |
54 |
55 |
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 |
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 | |
154 |
155 |
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 | |
330 |
331 |
332 |
333 |
334 |
335 |
336 | <%= link_to("\xc2\xab " + l(:label_previous),
337 | {:params => params.merge(@gantt.params_previous)},
338 | :accesskey => accesskey(:previous)) %>
339 | |
340 |
341 | <%= link_to(l(:label_next) + " \xc2\xbb",
342 | {:params => params.merge(@gantt.params_next)},
343 | :accesskey => accesskey(:next)) %>
344 | |
345 |
346 |
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 |
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 |
--------------------------------------------------------------------------------