Approval workflows #325 & #326

This commit is contained in:
Karel Pičman 2014-12-08 13:20:04 +01:00
parent ef11baad8e
commit 69dc408d2e
9 changed files with 154 additions and 83 deletions

View File

@ -37,7 +37,8 @@ class DmsfController < ApplicationController
@file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project) @file_manipulation_allowed = User.current.allowed_to?(:file_manipulation, @project)
@file_delete_allowed = User.current.allowed_to?(:file_delete, @project) @file_delete_allowed = User.current.allowed_to?(:file_delete, @project)
@force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project) @force_file_unlock_allowed = User.current.allowed_to?(:force_file_unlock, @project)
@workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0 @workflows_available = DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', @project.id]).count > 0
@file_approval_allowed = User.current.allowed_to?(:file_approval, @project)
unless @folder unless @folder
if params[:custom_field_id].present? && params[:custom_value].present? if params[:custom_field_id].present? && params[:custom_value].present?

View File

@ -27,11 +27,7 @@ class DmsfWorkflowsController < ApplicationController
layout :workflows_layout layout :workflows_layout
def index def index
if @project @workflow_pages, @workflows = paginate DmsfWorkflow.global.sorted, :per_page => 25
@workflow_pages, @workflows = paginate DmsfWorkflow.where(:project_id => @project.id), :per_page => 25
else
@workflow_pages, @workflows = paginate DmsfWorkflow.where(:project_id => nil), :per_page => 25
end
end end
def action def action
@ -210,22 +206,24 @@ class DmsfWorkflowsController < ApplicationController
@dmsf_workflow.name = params[:dmsf_workflow][:name] @dmsf_workflow.name = params[:dmsf_workflow][:name]
elsif params[:dmsf_workflow_id].present? elsif params[:dmsf_workflow_id].present?
wf = DmsfWorkflow.find_by_id params[:dmsf_workflow_id] wf = DmsfWorkflow.find_by_id params[:dmsf_workflow_id]
@dmsf_workflow.name = "#{wf.name}-#{@project.identifier}" if wf @dmsf_workflow.name = wf.name if wf
end end
render :layout => !request.xhr? render :layout => !request.xhr?
end end
def create def create
if params[:dmsf_workflow_id] && params[:dmsf_workflow] if params[:dmsf_workflow]
wf = DmsfWorkflow.find_by_id params[:dmsf_workflow_id] if (params[:dmsf_workflow_id].to_i > 0)
@dmsf_workflow = wf.copy_to @project, params[:dmsf_workflow][:name] wf = DmsfWorkflow.find_by_id params[:dmsf_workflow_id]
else @dmsf_workflow = wf.copy_to(@project, params[:dmsf_workflow][:name]) if wf
@dmsf_workflow = DmsfWorkflow.new(:name => params[:name], else
:project_id => @project.id) @dmsf_workflow = DmsfWorkflow.new(:name => params[:dmsf_workflow][:name])
@dmsf_workflow.save @dmsf_workflow.project_id = @project.id if @project
@dmsf_workflow.save
end
end end
if request.post? && @dmsf_workflow.valid? if request.post? && @dmsf_workflow && @dmsf_workflow.valid?
flash[:notice] = l(:notice_successful_create) flash[:notice] = l(:notice_successful_create)
if @project if @project
redirect_to settings_project_path(@project, :tab => 'dmsf_workflow') redirect_to settings_project_path(@project, :tab => 'dmsf_workflow')

View File

@ -49,20 +49,35 @@ module DmsfWorkflowsHelper
def dmsf_workflows_for_select(project, dmsf_workflow_id) def dmsf_workflows_for_select(project, dmsf_workflow_id)
options = Array.new options = Array.new
DmsfWorkflow.where(['project_id = ? OR project_id IS NULL', project.id]).each do |wf| DmsfWorkflow.sorted.where(['project_id = ? OR project_id IS NULL', project.id]).each do |wf|
options << [wf.name, wf.id] if wf.project_id
options << [wf.name, wf.id]
else
options << ["#{wf.name} (global)", wf.id]
end
end end
options_for_select(options, :selected => dmsf_workflow_id) options_for_select(options, :selected => dmsf_workflow_id)
end end
def dmsf_all_workflows_for_select(dmsf_workflow_id) def dmsf_all_workflows_for_select(dmsf_workflow_id)
options = Array.new options = Array.new
options << ['', 0] options << ['', 0]
DmsfWorkflow.where('project_id IS NOT NULL').each do |wf| DmsfWorkflow.sorted.all.each do |wf|
if User.current.allowed_to?(:manage_workflows, wf.project) if wf.project_id
prj = Project.find_by_id wf.project_id
if User.current.allowed_to?(:manage_workflows, prj)
# Local approval workflows
if prj
options << ["#{wf.name} (#{prj.name})", wf.id]
else
options << [wf.name, wf.id]
end
end
else
# Global approval workflows
options << [wf.name, wf.id] options << [wf.name, wf.id]
end end
end end
options_for_select(options, :selected => dmsf_workflow_id) options_for_select(options, :selected => dmsf_workflow_id)
end end

View File

@ -18,11 +18,40 @@
class DmsfWorkflow < ActiveRecord::Base class DmsfWorkflow < ActiveRecord::Base
has_many :dmsf_workflow_steps, :dependent => :destroy, :order => 'step ASC, operator DESC' has_many :dmsf_workflow_steps, :dependent => :destroy, :order => 'step ASC, operator DESC'
validates_uniqueness_of :name, :case_sensitive => false scope :sorted, lambda { order('name ASC') }
scope :global, lambda { where('project_id IS NULL') }
validate :name_validation
validates :name, :presence => true validates :name, :presence => true
validates_length_of :name, :maximum => 255 validates_length_of :name, :maximum => 255
def name_validation
if self.project_id
if self.id
if (DmsfWorkflow.where(['(project_id IS NULL OR (project_id = ? AND id != ?)) AND name = ?',
self.project_id, self.name, self.id]).count > 0)
errors.add(:name, l('activerecord.errors.messages.taken'))
end
else
if (DmsfWorkflow.where(['(project_id IS NULL OR project_id = ?) AND name = ?',
self.project_id, self.name]).count > 0)
errors.add(:name, l('activerecord.errors.messages.taken'))
end
end
else
if self.id
if DmsfWorkflow.where(['name = ? AND id != ?', self.name, self.id]).count > 0
errors.add(:name, l('activerecord.errors.messages.taken'))
end
else
if DmsfWorkflow.where(:name => self.name).count > 0
errors.add(:name, l('activerecord.errors.messages.taken'))
end
end
end
end
STATE_NONE = nil STATE_NONE = nil
STATE_ASSIGNED = 3 STATE_ASSIGNED = 3
STATE_WAITING_FOR_APPROVAL = 1 STATE_WAITING_FOR_APPROVAL = 1
@ -205,13 +234,9 @@ class DmsfWorkflow < ActiveRecord::Base
end end
def copy_to(project, name = nil) def copy_to(project, name = nil)
new_wf = self.dup new_wf = self.dup
if name new_wf.name = name if name
new_wf.name = name new_wf.project_id = project ? project.id : nil
else
new_wf.name << "-#{project.identifier}"
end
new_wf.project_id = project.id
if new_wf.save if new_wf.save
self.dmsf_workflow_steps.each do |step| self.dmsf_workflow_steps.each do |step|
step.copy_to(new_wf) step.copy_to(new_wf)

View File

@ -55,7 +55,7 @@
</td> </td>
<td class="version"><%= file.last_revision.version %></td> <td class="version"><%= file.last_revision.version %></td>
<td class="workflow"> <td class="workflow">
<% if wf && @file_manipulation_allowed %> <% if wf && @file_approval_allowed %>
<%= link_to( <%= link_to(
file.last_revision.workflow_str(false), file.last_revision.workflow_str(false),
log_dmsf_workflow_path( log_dmsf_workflow_path(
@ -70,61 +70,64 @@
</td> </td>
<td class="author"><%= h(file.last_revision.user) %></td> <td class="author"><%= h(file.last_revision.user) %></td>
<td class="actions"> <td class="actions">
<% if @file_manipulation_allowed %> <% if @file_manipulation_allowed || @file_approval_allowed %>
<div class="right_icon_box"> <div class="right_icon_box">
<% unless locked %> <% if @file_manipulation_allowed %>
<%= link_to('', <% unless locked %>
dmsf_file_path(:id => file),
:title => l(:link_details, :title => h(file.last_revision.title)),
:class => 'icon icon-dmsf-file-details') %>
<%= link_to('',
lock_dmsf_files_path(:id => file),
:title => l(:title_lock_file),
:class => 'icon icon-dmsf-lock') %>
<% if file.notification %>
<%= link_to('', <%= link_to('',
notify_deactivate_dmsf_files_path(:id => file), dmsf_file_path(:id => file),
:title => l(:title_notifications_active_deactivate), :title => l(:link_details, :title => h(file.last_revision.title)),
:class => 'icon icon-notification-on') %> :class => 'icon icon-dmsf-file-details') %>
<% else %>
<%= link_to('', <%= link_to('',
notify_activate_dmsf_files_path(:id => file), lock_dmsf_files_path(:id => file),
:title => l(:title_notifications_not_active_activate), :title => l(:title_lock_file),
:class => 'icon icon-notification-off') %> :class => 'icon icon-dmsf-lock') %>
<% end %> <% if file.notification %>
<% if link %>
<%= link_to('',
dmsf_link_path(link),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:title_delete),
:class => 'icon icon-del') %>
<% else %>
<% if @file_delete_allowed %>
<%= link_to('', <%= link_to('',
dmsf_file_path(:id => file), notify_deactivate_dmsf_files_path(:id => file),
:title => l(:title_notifications_active_deactivate),
:class => 'icon icon-notification-on') %>
<% else %>
<%= link_to('',
notify_activate_dmsf_files_path(:id => file),
:title => l(:title_notifications_not_active_activate),
:class => 'icon icon-notification-off') %>
<% end %>
<% if link %>
<%= link_to('',
dmsf_link_path(link),
:data => {:confirm => l(:text_are_you_sure)}, :data => {:confirm => l(:text_are_you_sure)},
:method => :delete, :method => :delete,
:title => l(:title_delete), :title => l(:title_delete),
:class => 'icon icon-del') unless locked_for_user %> :class => 'icon icon-del') %>
<% else %>
<% if @file_delete_allowed %>
<%= link_to('',
dmsf_file_path(:id => file),
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:title_delete),
:class => 'icon icon-del') unless locked_for_user %>
<% else %>
<span class="icon"></span>
<% end %>
<% end %>
<% else %>
<span class="icon"></span>
<% if (!locked_for_user || @force_file_unlock_allowed) && file.unlockable? %>
<%= link_to('',
unlock_dmsf_files_path(:id => file),
:title => l(:title_unlock_file),
:class => 'icon icon-dmsf-unlock')%>
<% else %> <% else %>
<span class="icon"></span> <span class="icon"></span>
<% end %> <% end %>
<% end %> <span class="icon icon-margin-left"></span>
<% else %>
<span class="icon"></span>
<% if (!locked_for_user || @force_file_unlock_allowed) && file.unlockable? %>
<%= link_to('',
unlock_dmsf_files_path(:id => file),
:title => l(:title_unlock_file),
:class => 'icon icon-dmsf-unlock')%>
<% else %>
<span class="icon"></span> <span class="icon"></span>
<% end %> <% end %>
<span class="icon icon-margin-left"></span>
<span class="icon"></span>
<% end %> <% end %>
<% case file.last_revision.workflow %> <% if @file_approval_allowed %>
<% case file.last_revision.workflow %>
<% when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL %> <% when DmsfWorkflow::STATE_WAITING_FOR_APPROVAL %>
<% if wf %> <% if wf %>
<% assignments = wf.next_assignments(file.last_revision.id) %> <% assignments = wf.next_assignments(file.last_revision.id) %>
@ -178,7 +181,8 @@
:class => 'icon icon-dmsf-none', :class => 'icon icon-dmsf-none',
:remote => true) %> :remote => true) %>
<% end %> <% end %>
<% end %> <% end %>
<% end %>
</div> </div>
<% end %> <% end %>
</td> </td>

View File

@ -1,6 +1,6 @@
<%# Redmine plugin for Document Management System "Features" <%# Redmine plugin for Document Management System "Features"
# #
# Copyright (C) 2013 Karel Pičman <karel.picman@kontron.com> # Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -16,7 +16,7 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%> # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.%>
<% @workflows = DmsfWorkflow.where(:project_id => @project.id) if @project && @workflows.nil? %> <% @workflows = DmsfWorkflow.sorted.where(:project_id => @project.id) if @project && @workflows.nil? %>
<div class="contextual"> <div class="contextual">
<% if @project %> <% if @project %>

View File

@ -42,6 +42,6 @@
<script type="text/javascript"> <script type="text/javascript">
$('#dmsf_workflow_id').change(function () { $('#dmsf_workflow_id').change(function () {
$('#content').load("<%= url_for(:action => 'new', :project_id => @project.id) %>", $('#new_dmsf_workflow').serialize()); $('#content').load("<%= url_for(:action => 'new', :project_id => @project) %>", $('#new_dmsf_workflow').serialize());
}); });
</script> </script>

View File

@ -0,0 +1,27 @@
# Redmine plugin for Document Management System "Features"
#
# Copyright (C) 2011-14 Karel Pičman <karel.picman@kontron.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
class RemoveUniquenessFromWf < ActiveRecord::Migration
def up
remove_index :dmsf_workflows, :name
end
def down
add_index :dmsf_workflows, [:name], :unique => true
end
end

11
init.rb
View File

@ -26,7 +26,7 @@ Redmine::Plugin.register :redmine_dmsf do
name 'DMSF' name 'DMSF'
author 'Vit Jonas / Daniel Munn / Karel Picman' author 'Vit Jonas / Daniel Munn / Karel Picman'
description 'Document Management System Features' description 'Document Management System Features'
version '1.4.9 stable' version '1.5.1 devel'
url 'http://www.redmine.org/plugins/dmsf' url 'http://www.redmine.org/plugins/dmsf'
author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors' author_url 'https://github.com/danmunn/redmine_dmsf/graphs/contributors'
@ -67,14 +67,15 @@ Redmine::Plugin.register :redmine_dmsf do
{:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, :notify_activate, :notify_deactivate, :delete_entries, :restore]} {:dmsf => [:new, :create, :delete, :edit, :save, :edit_root, :save_root, :lock, :unlock, :notify_activate, :notify_deactivate, :delete_entries, :restore]}
permission :file_manipulation, permission :file_manipulation,
{:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore], {:dmsf_files => [:create_revision, :lock, :unlock, :delete_revision, :notify_activate, :notify_deactivate, :restore],
:dmsf_upload => [:upload_files, :upload_file, :commit_files], :dmsf_upload => [:upload_files, :upload_file, :commit_files],
:dmsf_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment],
:dmsf_links => [:new, :create, :destroy, :restore] :dmsf_links => [:new, :create, :destroy, :restore]
} }
permission :file_delete, { :dmsf => [:trash], :dmsf_files => [:delete]} permission :file_delete, { :dmsf => [:trash], :dmsf_files => [:delete]}
permission :force_file_unlock, {}
permission :file_approval,
{:dmsf_workflows => [:action, :new_action, :autocomplete_for_user, :start, :assign, :assignment]}
permission :manage_workflows, permission :manage_workflows,
{:dmsf_workflows => [:index, :new, :create, :destroy, :show, :add_step, :remove_step, :reorder_steps, :update]} {:dmsf_workflows => [:index, :new, :create, :destroy, :show, :add_step, :remove_step, :reorder_steps, :update]}
permission :force_file_unlock, {}
end end
# Administration menu extension # Administration menu extension