例えば、(企業組織ではPDCAを回すために、)バグ管理票なるもので、そのバグの原因や修正区分、そのバグの混入工程や発見工程なんかの属性を管理することがある。これをチケットで管理する場合、カスタムフィールドでこれらの属性を定義することになる。そこで問題となるのが、Redmineのカスタムフィールドで「必須」にするとどのステータスにおいても必須になってしまう仕様だ。
Redmineの場合、必須チェックはステータスと連動させることができない。例えば前述の「原因」項目の場合、チケット登録時(バグ発見時)には不明な場合がほとんどで選択できない。ただ、チケット入力時には不要な情報でも、クローズする時には必須にしたい項目でもある。
このような、プロジェクト管理要素の強い属性は、チケットを処理している時には、(エンジニアにとってあまり重要に感じられないこともあり、)必須にしないと選択し忘れたり、後で入れようと思ったりされてしまう。ただ、数千ものバグを抱えるようなプロジェクトでは、前述の属性を後でまとめて入力するのは不可能に近い。やはりこれらは遅くともチケットクローズ時に確実に付加させてておくべき情報だ。
そのためには、ステータス別にカスタムフィールドの入力チェックをでききるようにしなければならない。以下がその改造。
<Redmineでの設定>
- データベースマイグレートが必要。(rake db:migrate RAILS_ENV="production")
- 管理→カスタムフィールで、チケットのカスタムフィールドに対し、デフォルト(プロジェクト登録時)で必須にするかどうかを設定する。プロジェクト登録時に必須/任意の設定は可能。
- プロジェクトメニューの設定→概要 で、ステータス別に必須にするカスタムフィールドにチェックを入れることで、必須設定する。
<Redmineソースの改造>(v.1.3.0ベース)
- 管理→カスタムフィールド の設定画面に、デフォルトで必須にするステータスを選択するチェックボックスを表示する。
- プロジェクトメニューの設定→概要 の画面に、チケットのカスタムフィールド毎に、ステータス別に必須/任意設定するチェックボックスを表示する。
- チケット登録・更新時に、ステータスに応じて必須設定されたカスタムフィールドの入力チェックを行う。
- custom_fieldsテーブルに、デフォルトで入力必須にするかどうかを管理するカラムを追加。
- プロジェクト別にステータス別必須カスタムフィールドを管理する新規テーブルを作成。
end def create + return unless validate_required_fields(@issue.status.id) call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue }) if @issue.save attachments = Attachment.attach_files(@issue, params[:attachments]) call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue}) : : def update update_issue_from_params + return unless validate_required_fields(params[:issue][:status_id].to_i) if @issue.save_issue_with_child_records(params, @time_entry) render_attachment_warning_if_needed(@issue) flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record? attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values] attributes end : : + def validate_required_fields(issue_status) + if params.nil? || params.empty? || ((params.include? 'issue') == false) || ((params[:issue].include? 'custom_field_values') == false) + return true + end + required_custom_fields = get_required_custom_field + issue_custom_fields = IssueCustomField.find(:all) + + #check issue_status.to_s if this is required_custom_fields's Hash key or not. + tmpkeys = required_custom_fields.keys + tmpflg = 0 + for i in 0 .. tmpkeys.length-1 + if tmpkeys[i] == issue_status.to_s + tmpflg = 1 + break + end + end + if tmpflg == 0 + # issue_status.to_s is not Hash key + return true + end + + if required_custom_fields[issue_status.to_s].size == 0 || issue_custom_fields == nil + return true + end + required_custom_fields[issue_status.to_s].each do |required| + issue_custom_fields.each do |issue_custom_field| + if required == issue_custom_field.name + unless (params[:issue][:custom_field_values].include? issue_custom_field.id.to_s) + break + end + if params[:issue][:custom_field_values][issue_custom_field.id.to_s] == "" + tmp = t 'activerecord.errors.messages.blank' + flash.now[:error] = required + " " + tmp + @priorities = IssuePriority.all + if @action_name == "update" + render :template => 'issues/edit', :layout => !request.xhr? + elsif @action_name == "create" + render :template => 'issues/new', :layout => !request.xhr? + end + return false + end + end + end + end + return true + end end
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position") @trackers = Tracker.all @project = Project.new(params[:project]) + @required_custom_field = get_required_custom_field end verify :method => :post, :only => :create, :render => {:nothing => true, :status => :method_not_allowed } : : :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}", :order => 'name') @source_project = Project.find(params[:id]) + @issue_status = IssueStatus.find( :all ,:order => 'position') + @required_custom_field = get_required_custom_field if request.get? @project = Project.copy_from(@source_project) if @project : : @project.safe_attributes = params[:project] if validate_parent_id && @project.copy(@source_project, :only => params[:only]) @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') + save_required_custom_field flash[:notice] = l(:notice_successful_create) redirect_to :controller => 'projects', :action => 'settings', :id => @project elsif !@project.new_record? : : @trackers = Tracker.all @repository ||= @project.repository @wiki ||= @project.wiki + @required_custom_field = get_required_custom_field end def edit : : @project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id') respond_to do |format| format.html { + save_required_custom_field flash[:notice] = l(:notice_successful_update) redirect_to :action => 'settings', :id => @project } : : end true end + + def save_required_custom_field + hash_others = Hash.new + tmp_array = [] + hash_others = {} + @issue_status = IssueStatus.find( :all ,:order => 'position') + for tmp_issue_status in @issue_status + tmp_array = [] + if params[:required_custom_field] != nil + if params[:required_custom_field].include? tmp_issue_status.id.to_s + tmp_array = params[:required_custom_field][tmp_issue_status.id.to_s].keys + end + end + hash_others[tmp_issue_status.id.to_s] = tmp_array + end + + required_custom_field = RequiredCustomField.find(:first, :conditions => ["project_id = ?", @project.id]) + if required_custom_field == nil + required_custom_field = RequiredCustomField.new(:project_id => @project.id, + :others => hash_others) + else + required_custom_field.others = hash_others + end + if required_custom_field.save == false + required_custom_field.connection.rollback_db_transaction + end + end + end
sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) l("label_version_sharing_#{sharing}") end + + def required_custom_field_checkbox_tag(project, custom_field) + s = '' + @issue_status.each do |status| + custom_field.is_required ? checked = true : checked = false + unless @required_custom_field[status.id.to_s] == nil + @required_custom_field[status.id.to_s].each do |cf| + checked = true if cf == custom_field.name + end + end + s << "\n" + s << check_box_tag('required_custom_field[' + status.id.to_s + '][' + custom_field.name + ']', + custom_field.id, + checked, + :disabled => custom_field.is_required?) + "\n \n" + end + s + end + + def get_required_custom_field + required_custom_field = RequiredCustomField.find(:first, :conditions => ["project_id = ?", @project.id]) + @issue_status = IssueStatus.find( :all ,:order => 'position') + required_custom_field_other = Hash.new + if required_custom_field.nil? + # Registering new project, set default required custom feilds. + @issue_status.each do |status| + cfnames = [] + @issue_custom_fields.each do |cf| + cfnames << cf.name if !cf.required_issue_status_ids.nil? && YAML.load(cf.required_issue_status_ids).include?(status.id.to_s) + end + required_custom_field_other[status.id.to_s] = cfnames + end + else + # On existing project's setting + @issue_status.each do |status| + required_custom_field_other[status.id.to_s] = required_custom_field[status.id.to_s] + end + end + required_custom_field_other + end end
+class RequiredCustomField < ActiveRecord::Base + serialize :others + def [](attr_name) + if attribute_present? attr_name + super + else + others ? others[attr_name] : nil + end + end +end
<% end %> <%= hidden_field_tag "custom_field[tracker_ids][]", '' %> </fieldset> - <p><%= f.check_box :is_required %></p> + <p><%= f.check_box :is_required %> + <%= l(:label_all) %>: <i><%= l(:text_required_all) %></i><br /> + <%=l(:label_applied_status)%>: <i><%= l(:text_required_specific) %></i><br /> + <% for status in IssueStatus.find(:all ,:order => 'position') -%> + <%= check_box_tag "custom_field[required_issue_status_ids][]", + status.id.to_s, + (YAML.load(@custom_field.required_issue_status_ids).include?(status.id.to_s) unless @custom_field.required_issue_status_ids.nil?), + :onchange => 'if (this.checked){$("custom_field_is_required").checked = false;}' + %> + <%= h(status.name) %><br /> + <% end -%> + </p> + <p><%= f.check_box :is_for_all %></p> <p><%= f.check_box :is_filter %></p> <p><%= f.check_box :searchable %></p>
</fieldset> <% end %> <% end %> + +<fieldset class="box"><legend><%=l(:field_is_required)%></legend> + <table class="list"> + <thead> + <tr> + <th><%=l(:label_custom_field)%></th> + <% for tmp_issue_status in @issue_status %> + <th><%=tmp_issue_status%></th> + <% end %> + </tr> + </thead> + <tbody> + <% @issue_custom_fields.each do |custom_field| %> + <tr class="<%= cycle("odd", "even") %>"> + <td class="name" align="left"><%= custom_field.name %></td> + <%= required_custom_field_checkbox_tag(@project, custom_field) %> + </tr> + <% end %> + </tbody> + </table> +</fieldset> + <!--[eoform:project]-->
text_scm_command_version: Version text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it. text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel. + text_required_all: "Will be required field in all satatuses, and be unchangeable from requierd field in the project setting." + text_required_specific: "Will be default required field in checked statuses, and be changeable for requierd or not in the project setting." default_role_manager: Manager default_role_developer: Developer
text_scm_command_version: バージョン text_scm_config: バージョン管理システムのコマンドをconfig/configuration.ymlで設定できます。設定後、Redmineを再起動してください。 text_scm_command_not_available: バージョン管理システムのコマンドが利用できません。管理画面にて設定を確認してください。 + text_required_all: "すべてのステータスで必須項目とし、プロジェクトの設定では変更できないようになります。" + text_required_specific: "チェックしたステータスをデフォルトの必須項目とし、プロジェクトの設定で変更できるようになります。" default_role_manager: 管理者 default_role_developer: 開発者
+class AddCustomFieldsRequiredIssueStatusIds < ActiveRecord::Migration + def self.up + add_column :custom_fields, :required_issue_status_ids, :text + end + + def self.down + remove_column :custom_fields, :required_issue_status_ids + end +end
+class CreateRequiredCustomFields < ActiveRecord::Migration + def self.up + create_table :required_custom_fields do |t| + t.column "project_id", :integer, :default => 0, :null => false + t.column "others", :text + end + add_index :required_custom_fields, :project_id + end + + def self.down + drop_table :required_custom_fields + end +end
通りすがりのものですが
返信削除Redmine 2.2ではカスタムクエリー以外に、「管理⇒ワークフロー」の「フィールドに対する権限」タブでステータスごとの入力制御が行えます。
※それ以外のバージョンは使っていないのでわかりません。
コメントありがとうございます。
削除ステータス別入力制御はv.2.1から実装された機能ですが、プロジェクト別に設定できません。
私の所では数百プロジェクトで運用しており、プロジェクト別に設定できることが最大要件となっております。
これ以外にもいろいろ事情があって、v2系に移行できなくなっており、v1.4系を使い続けることになると思います。
V.1.4系での当機能の実装については次の記事になります。
http://redminist.blogspot.jp/2012/09/v144.html
ちょっと検索して通りがかったものですm(_ _)m
返信削除Redmine2.3でも同じようなカスタマイズは可能ですか。
カスタムフィールドの配置に苦戦です・・・
コメントありがとうございます。
返信削除本家Redmine2.3(2.2から)は類似の機能が実装されていますが、実装のアプローチがこれとは全く違っています。
なので、難しいのではないでしょうか。。。
http://www.redmine.org/issues/3521
本家はワークフローに追加定義していますが、当方は別途プロジェクトIDからなる管理テーブルを起こしています。