Index: test/unit/helpers/issue_schedules_helper_test.rb =================================================================== --- test/unit/helpers/issue_schedules_helper_test.rb (revision 0) +++ test/unit/helpers/issue_schedules_helper_test.rb (revision 0) @@ -0,0 +1,4 @@ +require 'test_helper' + +class IssueSchedulesHelperTest < ActionView::TestCase +end Index: test/unit/issue_schedule_test.rb =================================================================== --- test/unit/issue_schedule_test.rb (revision 0) +++ test/unit/issue_schedule_test.rb (revision 0) @@ -0,0 +1,8 @@ +require 'test_helper' + +class IssueScheduleTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end Index: test/functional/issue_schedules_controller_test.rb =================================================================== --- test/functional/issue_schedules_controller_test.rb (revision 0) +++ test/functional/issue_schedules_controller_test.rb (revision 0) @@ -0,0 +1,8 @@ +require 'test_helper' + +class IssueSchedulesControllerTest < ActionController::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end Index: test/fixtures/issue_schedules.yml =================================================================== --- test/fixtures/issue_schedules.yml (revision 0) +++ test/fixtures/issue_schedules.yml (revision 0) @@ -0,0 +1,23 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +one: + project_id: 1 + issue_id: 1 + assigned_to_id: 1 + author_id: 1 + subject: MyString + interval_number: 1 + interval_units: MyString + last_assigned_date: 2012-02-14 14:26:40 + next_run_date: 2012-02-14 14:26:40 + +two: + project_id: 1 + issue_id: 1 + assigned_to_id: 1 + author_id: 1 + subject: MyString + interval_number: 1 + interval_units: MyString + last_assigned_date: 2012-02-14 14:26:40 + next_run_date: 2012-02-14 14:26:40 Index: app/helpers/issue_schedules_helper.rb =================================================================== --- app/helpers/issue_schedules_helper.rb (revision 0) +++ app/helpers/issue_schedules_helper.rb (revision 0) @@ -0,0 +1,2 @@ +module IssueSchedulesHelper +end Index: app/models/journal.rb =================================================================== --- app/models/journal.rb (revision 8667) +++ app/models/journal.rb (working copy) @@ -85,4 +85,16 @@ def notify=(arg) @notify = arg end + # copies a set of journal notes from one object to another + def self.copy_notes(objToId, objFromId) + transaction do + connection.insert "INSERT INTO #{Journal.table_name} (journalized_id,journalized_type,user_id,notes,created_on)" + + " SELECT #{objToId}, journalized_type,user_id,notes,created_on" + + " FROM #{Journal.table_name}" + + " WHERE journalized_id = #{objFromId}" + + + end + true + end end Index: app/models/issue_schedule.rb =================================================================== --- app/models/issue_schedule.rb (revision 0) +++ app/models/issue_schedule.rb (revision 0) @@ -0,0 +1,4 @@ +class IssueSchedule < ActiveRecord::Base + belongs_to :issue + +end Index: app/models/issue.rb =================================================================== --- app/models/issue.rb (revision 8667) +++ app/models/issue.rb (working copy) @@ -29,6 +29,7 @@ has_many :journals, :as => :journalized, :dependent => :destroy has_many :time_entries, :dependent => :delete_all + has_many :issue_schedules, :dependent=> :delete_all has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all @@ -50,6 +51,7 @@ :author_key => :author_id DONE_RATIO_OPTIONS = %w(issue_field issue_status) + INTERVAL_UNITS = ['day','week', 'month'] attr_reader :current_journal @@ -86,7 +88,7 @@ before_save :close_duplicates, :update_done_ratio_from_issue_status after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal after_destroy :update_parent_attributes - + # Returns a SQL conditions string used to find all issues visible by the specified user def self.visible_condition(user, options={}) Project.allowed_to_condition(user, :view_issues, options) do |role, user| @@ -141,7 +143,50 @@ self.status = issue.status self end - + def copy_attachment(arg) + + issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg) + + if issue.attachments.any? + Attachment.copy_files(id,issue.id) + end + + end + + def copy_notes(arg) + issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg) + + if issue.journals.present? + Journal.copy_notes(id,issue.id) + end + + end + + # repeats and schedule issues + def repeat_issue(params) + + Issue.transaction do + #if User.current.allowed_to?(:schedule_issue, project) + @schedule_entry = IssueSchedule.new + @schedule_entry.project_id = project + @schedule_entry.tracker_id = self.tracker_id + @schedule_entry.issue_id = self.id + @schedule_entry.assigned_to_id = self.assigned_to_id + @schedule_entry.author_id = self.author_id + @schedule_entry.subject = self.subject + @schedule_entry.interval_number = params[:issue][:issue_schedules][:interval_number] + @schedule_entry.interval_units = params[:issue][:issue_schedules][:interval_units] + @schedule_entry.last_assigned_date = User.current.today + @schedule_entry.next_run_date = params[:issue][:issue_schedules][:next_run_date] + self.issue_schedules << @schedule_entry + #end + end + true + + end + + + # Moves/copies an issue to a new project and tracker # Returns the moved/copied issue on success, false on failure def move_to_project(*args) Index: app/models/attachment.rb =================================================================== --- app/models/attachment.rb (revision 8667) +++ app/models/attachment.rb (working copy) @@ -58,6 +58,7 @@ def file=(incoming_file) unless incoming_file.nil? @temp_file = incoming_file + if @temp_file.size > 0 self.filename = sanitize_filename(@temp_file.original_filename) self.disk_filename = Attachment.disk_filename(filename) @@ -149,6 +150,7 @@ if attachments && attachments.is_a?(Hash) attachments.each_value do |attachment| file = attachment['file'] + logger.info("filename '#{file}'") next unless file && file.size > 0 a = Attachment.create(:container => obj, :file => file, @@ -172,12 +174,24 @@ |att| att.filename.downcase == filename.downcase } end + + # copies a set of files attached from one object to another + def self.copy_files(objToId, objFromId) + transaction do + connection.insert "INSERT INTO #{Attachment.table_name} (container_id,container_type,filename,disk_filename,filesize,content_type,digest,downloads,author_id,created_on,description)" + + " SELECT #{objToId}, container_type,filename,disk_filename,filesize,content_type,digest,downloads,author_id,created_on,description" + + " FROM #{Attachment.table_name}" + + " WHERE container_id = #{objFromId}" + end + true + end private def sanitize_filename(value) # get only the filename, not the whole path + just_filename = value.gsub(/^.*(\\|\/)/, '') - + # Finally, replace invalid characters with underscore @filename = just_filename.gsub(/[\/\?\%\*\:\|\"\'<>]+/, '_') end Index: app/controllers/issues_controller.rb =================================================================== --- app/controllers/issues_controller.rb (revision 8667) +++ app/controllers/issues_controller.rb (working copy) @@ -123,6 +123,7 @@ @edit_allowed = User.current.allowed_to?(:edit_issues, @project) @priorities = IssuePriority.active @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project) + respond_to do |format| format.html { render :template => 'issues/show' } format.api @@ -143,6 +144,7 @@ def create 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}) respond_to do |format| @@ -175,10 +177,12 @@ end def update + @issue.repeat_issue(params) if params[:repeat] update_issue_from_params 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? respond_to do |format| @@ -195,6 +199,7 @@ format.api { render_validation_errors(@issue) } end end + end # Bulk edit a set of issues @@ -293,6 +298,7 @@ @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil) @issue.init_journal(User.current, @notes) @issue.safe_attributes = params[:issue] + end # TODO: Refactor, lots of extra code in here Index: app/controllers/issue_schedules_controller.rb =================================================================== --- app/controllers/issue_schedules_controller.rb (revision 0) +++ app/controllers/issue_schedules_controller.rb (revision 0) @@ -0,0 +1,84 @@ +class IssueSchedulesController < ApplicationController + unloadable + + + before_filter :find_project + before_filter :find_issueschedule, :except => [:new, :index] + before_filter :load_users, :except => [:destroy] + + def index + if !params[:project_id] then return end + @project_identifier = params[:project_id] + @project = Project.find(params[:project_id]) + @issue_schedule = IssueSchedule.find_all_by_project_id(@project[:id]) + end + def update + if !params[:project_id] then return end + @issue_schedule = IssueSchedule.find(params[:id]) + @issue_schedule.update_attributes(params[:issue_schedules]) + + if @issue_schedule.save + flash[:notice] = "Issue Schedule is saved" + redirect_to :controller => 'issue_schedules', :action => 'index', :project_id=>params[:project_id] + end + end + def new + if !params[:project_id] then return end + @project = Project.find(params[:project_id]) + @issue_schedule = IssueSchedule.new(:project=>@project, :author_id=>User.current.id) + if request.post? + params[:issue_schedules][:project_id] = @project[:id] + @issue_schedule.attributes = params[:issue_schedules] + if @issue_schedule.save + flash[:notice] = "Issue Schedule is saved" + redirect_to :controller => 'issue_schedules', :action => 'index', :project_id=>params[:project_id] + end + end + + end + + def edit + if request.post? + @issue_schedule = IssueSchedule.find(params[:id]) + params[:issue_schedules][:project_id] = params[:project_id] + if request.post? + if @issue_schedule.update_attributes(params[:issue_schedules]) + flash[:notice] = "saved issue schedule" + redirect_to :action => 'index', :project_id => params[:project_id] + end + end + end + end + + def destroy + @issue_schedule = IssueSchedule.find(params[:id]) + @issue_schedule.destroy + redirect_to :action => 'index', :project_id => params[:project_id] + end +private + def find_issueschedule + @issue_schedule = IssueSchedule.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + def find_project + @project = Project.find(params[:project_id]) + end + + def load_users + # Get the users that are members in the project + # + #@users = User.find_by_sql('SELECT users.id, CONCAT(users.firstname, \' \', users.lastname) fullname FROM members INNER JOIN users + # ON members.user_id = users.id + # WHERE project_id = ' + @project[:id].to_s + ' + # AND status = 1 + # ORDER BY firstname ASC') + @users = [] + @project.members.each do |m| + @users << m.user + end + + end + + +end Index: app/views/issue_schedules/edit.html.erb =================================================================== --- app/views/issue_schedules/edit.html.erb (revision 0) +++ app/views/issue_schedules/edit.html.erb (revision 0) @@ -0,0 +1,9 @@ +

<%= l(:label_issue_schedules_edit) %>

+

Use this form to edit this task.

+<% labelled_tabular_form_for :issue_schedules, @issue_schedule, + :url => { :controller => 'issue_schedules', :action => 'update', :id=>@issue_schedule.id, :project_id => @project }, + :html => { :id => 'task-form' } do |f| %> +<%= render :partial => 'issue_schedules/form', :locals => { :f => f } %> +<%= submit_tag l(:button_update) %> +<%= link_to l(:button_cancel), :controller => 'issue_schedules',:action => 'index', :project_id => @project%> +<% end if @project %> Index: app/views/issue_schedules/index.html.erb =================================================================== --- app/views/issue_schedules/index.html.erb (revision 0) +++ app/views/issue_schedules/index.html.erb (revision 0) @@ -0,0 +1,34 @@ + +<% if @project_identifier %> + +

Issue Schedules

+

These are the currently scheduled issues

+ + + + + + + + <% @issue_schedule.each do |a| %> + + + + + + + <% end %> + +
<%= l(:label_subject) %><%= l(:label_next_run_date) %>  
<%= a.subject %><%= a.next_run_date %> + <%= link_to l(:button_edit), + {:controller => 'issue_schedules', :action => 'edit', :id => a.id, :project_id => @project}, + :class => 'icon icon-edit', + :accesskey => accesskey(:edit), + :onclick => 'Element.show("edit-task"); return false;' %> + + <%= link_to l(:button_delete), {:controller => 'issue_schedules', :action => 'destroy', :id => a.id, :project_id => @project}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %> +
+ +<% else %> +

No project ID supplied

+<% end %> Index: app/views/issue_schedules/destroy.html.erb =================================================================== --- app/views/issue_schedules/destroy.html.erb (revision 0) +++ app/views/issue_schedules/destroy.html.erb (revision 0) @@ -0,0 +1 @@ +

IssueSchedule#destroy

Index: app/views/issue_schedules/new.html.erb =================================================================== --- app/views/issue_schedules/new.html.erb (revision 0) +++ app/views/issue_schedules/new.html.erb (revision 0) @@ -0,0 +1 @@ +

IssueSchedule#new

Index: app/views/issue_schedules/_form.html.erb =================================================================== --- app/views/issue_schedules/_form.html.erb (revision 0) +++ app/views/issue_schedules/_form.html.erb (revision 0) @@ -0,0 +1,21 @@ +<%= error_messages_for 'issue_schedules' %> +<%= f.hidden_field :id %> +
+

<%= f.text_field :subject, :required => true, :size => 60 %>

+

+ + <% if @users.length > 0 %> +

<%= f.select :assigned_to_id, @users.collect {|t| [t.name, t.id]}, :required => true %>

+ + <% else %> + <%= l(:no_members_in_project) %> + <% end %> +

+

<%= f.text_field :interval_number, :required=> true, :size => 3 %>

+

<%= f.select :interval_units, Issue::INTERVAL_UNITS %>

+

<%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %>

+ +

<%= f.text_field :next_run_date %><%=calendar_for('issue_schedules_next_run_date')%>

+
+ + Index: app/views/issues/_sidebar.html.erb =================================================================== --- app/views/issues/_sidebar.html.erb (revision 8667) +++ app/views/issues/_sidebar.html.erb (working copy) @@ -11,6 +11,7 @@ <% if User.current.allowed_to?(:view_gantt, @project, :global => true) %> <%= link_to(l(:label_gantt), :controller => 'gantts', :action => 'show', :project_id => @project) %>
<% end %> + <%= link_to l(:label_issue_schedules), { :controller => 'issue_schedules', :action => 'index', :project_id => @project} %>
<%= call_hook(:view_issues_sidebar_planning_bottom) %> <%= render_sidebar_queries %> Index: app/views/issues/show.html.erb =================================================================== --- app/views/issues/show.html.erb (revision 8667) +++ app/views/issues/show.html.erb (working copy) @@ -108,6 +108,14 @@ <% end %> +
+<% if authorize_for('issues', 'edit') %> + + +<% end %> <% other_formats_links do |f| %> <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %> <%= f.link_to 'PDF' %> Index: app/views/issues/_edit.html.erb =================================================================== --- app/views/issues/_edit.html.erb (revision 8667) +++ app/views/issues/_edit.html.erb (working copy) @@ -1,5 +1,5 @@ <% labelled_tabular_form_for :issue, @issue, - :url => {:action => 'update', :id => @issue}, + :url => {:action => 'update',:id => @issue}, :html => {:id => 'issue-form', :class => nil, :method => :put, @@ -31,6 +31,7 @@ <% end %> <% end %> +
<%= l(:field_notes) %> <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %> Index: app/views/issues/_action_menu.html.erb =================================================================== --- app/views/issues/_action_menu.html.erb (revision 8667) +++ app/views/issues/_action_menu.html.erb (working copy) @@ -1,8 +1,9 @@
-<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> +<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'setVisible("repeat",false);showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %> <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'new', :issue_id => @issue}, :class => 'icon icon-time-add' %> <%= watcher_tag(@issue, User.current) %> <%= link_to_if_authorized l(:button_duplicate), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-duplicate' %> +<%= link_to_if_authorized l(:button_repeat), {:controller => 'issues', :action => 'edit', :project_id => @project, :repeat => @issue },:onclick => 'setVisible("update",false);showAndScrollTo("repeat", "notes"); return false;', :class => 'icon icon-duplicate' %> <%= link_to_if_authorized l(:button_copy), {:controller => 'issue_moves', :action => 'new', :id => @issue, :copy_options => {:copy => 't'}}, :class => 'icon icon-copy' %> <%= link_to_if_authorized l(:button_move), {:controller => 'issue_moves', :action => 'new', :id => @issue}, :class => 'icon icon-move' %> <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => issues_destroy_confirmation_message(@issue), :method => :post, :class => 'icon icon-del' %> Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (revision 8667) +++ config/locales/en.yml (working copy) @@ -317,6 +317,7 @@ field_root_directory: Root directory field_cvsroot: CVSROOT field_cvs_module: Module + field_interval_units: Interval Units setting_app_title: Application title setting_app_subtitle: Application subtitle @@ -464,6 +465,8 @@ label_issue_new: New issue label_issue_plural: Issues label_issue_view_all: View all issues + label_issue_schedules: View issue schedule + label_issue_schedules_edit: Edit issue schedule label_issues_by: "Issues by %{value}" label_issue_added: Issue added label_issue_updated: Issue updated @@ -689,6 +692,8 @@ label_changes_details: Details of all changes label_issue_tracking: Issue tracking label_spent_time: Spent time + label_interval_number: Interval Number + label_next_run_date: Next Run Date label_overall_spent_time: Overall spent time label_f_hour: "%{value} hour" label_f_hour_plural: "%{value} hours" @@ -859,6 +864,7 @@ button_activate: Activate button_sort: Sort button_log_time: Log time + button_repeat_detail: Schedule Issue button_rollback: Rollback to this version button_watch: Watch button_unwatch: Unwatch @@ -875,6 +881,7 @@ button_configure: Configure button_quote: Quote button_duplicate: Duplicate + button_repeat: Repeat button_show: Show button_edit_section: Edit this section button_export: Export Index: lib/tasks/task_scheduler.rake =================================================================== --- lib/tasks/task_scheduler.rake (revision 8667) +++ lib/tasks/task_scheduler.rake (working copy) @@ -1,9 +1,7 @@ namespace :redmine do - desc "List all permissions and the actions registered with them" - task :permissions => :environment do - puts "Permission Name - controller/action pairs" - Redmine::AccessControl.permissions.sort {|a,b| a.name.to_s <=> b.name.to_s }.each do |permission| - puts ":#{permission.name} - #{permission.actions.join(', ')}" - end + desc "Schedule issues" + task :check_issueschedule => :environment do + #puts "Scheduling Issues" + ScheduledTasksChecker.checktasks! end end Index: lib/scheduled_tasks_checker.rb =================================================================== --- lib/scheduled_tasks_checker.rb (revision 0) +++ lib/scheduled_tasks_checker.rb (revision 0) @@ -0,0 +1,18 @@ +class ScheduledTasksChecker + def self.checktasks! + IssueSchedule.all(:conditions=> ["next_run_date <= ? ", Time.now.to_date]).each do |task| + # print "assigning #{task.subject}" + issue = Issue.new(:project_id=>task.project_id,:tracker_id=>task.tracker_id, :assigned_to_id=>task.assigned_to_id, :author_id=>task.author_id, :subject=>task.subject); + issue.save + + copy_issue = Issue.find_by_id(task.issue_id) + issue.copy_from(copy_issue) + issue.copy_attachment(copy_issue) + issue.copy_notes(copy_issue) + interval = task.interval_number + units = task.interval_units + task.next_run_date = interval.send(units.downcase).from_now + task.save + end + end +end