Importación inicial
git-svn-id: https://192.168.0.254/svn/Rodax.redmine_rodax_crm/trunk@2 ff88604e-da85-c949-a72f-fc3aa3ba3724
This commit is contained in:
parent
766e08a527
commit
946162f5d0
33
README.rdoc
Normal file
33
README.rdoc
Normal file
@ -0,0 +1,33 @@
|
||||
= Contacts plugin
|
||||
|
||||
== Install
|
||||
|
||||
* Copy redmine_contacts_plugin to vendor/plugins on your redmine path or
|
||||
@svn co http://hgftr.ru/svn/repository/redmine/redmine_contacts/trunk/ redmine_contacts@
|
||||
* Run rake db:migrate_plugins RAILS_ENV=production
|
||||
* Run rake gems:install RAILS_ENV=production
|
||||
|
||||
Conflicted with redmine_customer plugin
|
||||
|
||||
== Uninstall
|
||||
|
||||
<pre>
|
||||
rake db:migrate:plugin NAME=redmine_contacts VERSION=0 RAILS_ENV=production
|
||||
rm -r vendor/plugins/redmine_contacts
|
||||
</pre>
|
||||
|
||||
|
||||
== Test
|
||||
rake db:drop RAILS_ENV=test_sqlite3
|
||||
rake db:migrate db:migrate_plugins RAILS_ENV=test_sqlite3
|
||||
rake test:plugins:integration PLUGIN=redmine_contacts RAILS_ENV=test_sqlite3
|
||||
rake test:plugins:functionals PLUGIN=redmine_contacts RAILS_ENV=test_sqlite3
|
||||
rake test:plugins PLUGIN=redmine_contacts RAILS_ENV=test_sqlite3
|
||||
|
||||
rake db:reset db:migrate_plugins test:plugins NAME=redmine_contacts RAILS_ENV=test
|
||||
|
||||
rake test:engines:all PLUGIN=redmine_contacts
|
||||
|
||||
=== Test API
|
||||
|
||||
curl -v -H "Content-Type: application/xml" -X POST --data "@contact.xml" -u bkv:Bxd2Hd5 http://localhost:3000/contacts.xml
|
||||
426
app/controllers/contacts_controller.rb
Normal file
426
app/controllers/contacts_controller.rb
Normal file
@ -0,0 +1,426 @@
|
||||
class ContactsController < ApplicationController
|
||||
unloadable
|
||||
|
||||
Mime::Type.register "text/x-vcard", :vcf
|
||||
|
||||
default_search_scope :contacts
|
||||
|
||||
before_filter :find_contact, :only => [:show, :edit, :update, :destroy, :edit_tags]
|
||||
before_filter :find_project, :only => [:new, :create]
|
||||
before_filter :authorize, :only => [:show, :edit, :update, :create, :new, :destroy, :edit_tags]
|
||||
before_filter :find_optional_project, :only => [:index, :contacts_notes]
|
||||
|
||||
accept_rss_auth :index, :show
|
||||
accept_api_auth :index, :show, :create, :update, :destroy
|
||||
|
||||
helper :attachments
|
||||
helper :contacts
|
||||
helper :watchers
|
||||
helper :deals
|
||||
helper :notes
|
||||
helper :custom_fields
|
||||
include WatchersHelper
|
||||
|
||||
def show
|
||||
@open_issues = @contact.issues.visible.open(:order => "#{Issue.table_name}.due_date DESC")
|
||||
source_id_cond = @contact.is_company ? Contact.order_by_name.find_all_by_company(@contact.first_name).map(&:id) << @contact.id : @contact.id
|
||||
@note = ContactNote.new
|
||||
@notes_pages, @notes = paginate :notes,
|
||||
:per_page => 30,
|
||||
:conditions => {:source_id => source_id_cond,
|
||||
:source_type => 'Contact'},
|
||||
:include => [:attachments],
|
||||
:order => "created_on DESC"
|
||||
|
||||
respond_to do |format|
|
||||
format.js if request.xhr?
|
||||
format.html { @contact.viewed }
|
||||
format.api
|
||||
format.atom { render_feed(@notes, :title => "#{@contact.name || Setting.app_title}: #{l(:label_note_plural)}") }
|
||||
format.xml { render :xml => @contact }
|
||||
format.json { render :text => @contact.to_json, :layout => false }
|
||||
format.vcf { send_data(contact_to_vcard(@contact), :filename => "#{@contact.name}.vcf", :type => 'text/x-vcard;', :disposition => 'attachment') }
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
find_contacts
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
last_notes
|
||||
find_tags
|
||||
end
|
||||
format.js { render :partial => "list", :layout => false }
|
||||
format.api
|
||||
format.xml { render :xml => find_contacts(false) }
|
||||
format.json { render :text => find_contacts(false).to_json, :layout => false }
|
||||
format.atom { render_feed(find_contacts(false), :title => "#{@project || Setting.app_title}: #{l(:label_contact_plural)}") }
|
||||
format.csv { send_data(contacts_to_csv(find_contacts(false)), :type => 'text/csv; header=present', :filename => 'contacts.csv') }
|
||||
format.vcf { send_data(contacts_to_vcard(find_contacts(false)), :filename => "contacts.vcf", :type => 'text/x-vcard;', :disposition => 'attachment') }
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
if @contact.update_attributes(params[:contact])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
attach_avatar
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :action => "show", :project_id => params[:project_id], :id => @contact }
|
||||
format.api { head :ok }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render "edit", :project_id => params[:project_id], :id => @contact }
|
||||
format.api { render_validation_errors(@contact) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @contact.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
else
|
||||
flash[:error] = l(:notice_unsuccessful_save)
|
||||
end
|
||||
redirect_to :action => "index", :project_id => params[:project_id]
|
||||
end
|
||||
|
||||
def new
|
||||
@duplicates = []
|
||||
@contact = Contact.new
|
||||
|
||||
@contact.attributes = params[:contact] if params[:contact] && params[:contact].is_a?(Hash)
|
||||
|
||||
# @contact.company = params[:company_name] if params[:company_name]
|
||||
# @contact.first_name = params[:first_name] if params[:first_name]
|
||||
# @contact.last_name = params[:last_name] if params[:last_name]
|
||||
# @contact.middle_name = params[:middle_name] if params[:middle_name]
|
||||
# @contact.email = params[:email] if params[:email]
|
||||
end
|
||||
|
||||
def create
|
||||
params[:contact].delete(:project_id)
|
||||
@contact = Contact.new(params[:contact])
|
||||
@contact.projects << @project
|
||||
@contact.author = User.current
|
||||
if @contact.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
attach_avatar
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :action => "show", :project_id => @project, :id => @contact }
|
||||
format.api { render :action => 'show', :status => :created, :location => contact_url(@contact) }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.api { render_validation_errors(@contact) }
|
||||
format.html { render :action => "new" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def edit_tags
|
||||
@contact.tags.clear
|
||||
@contact.update_attributes(params[:contact])
|
||||
redirect_to :action => 'show', :id => @contact, :project_id => @project
|
||||
end
|
||||
|
||||
def contacts_notes
|
||||
unless request.xhr?
|
||||
find_tags
|
||||
end
|
||||
# @notes = Comment.find(:all,
|
||||
# :conditions => { :commented_type => "Contact", :commented_id => find_contacts.map(&:id)},
|
||||
# :order => "updated_on DESC")
|
||||
|
||||
contacts = find_contacts(false)
|
||||
deals = find_deals
|
||||
|
||||
joins = " "
|
||||
joins << " LEFT OUTER JOIN #{Contact.table_name} ON #{Note.table_name}.source_id = #{Contact.table_name}.id AND #{Note.table_name}.source_type = 'Contact' "
|
||||
joins << " LEFT OUTER JOIN #{Deal.table_name} ON #{Note.table_name}.source_id = #{Deal.table_name}.id AND #{Note.table_name}.source_type = 'Deal' "
|
||||
|
||||
cond = "(1 = 1) "
|
||||
cond << "and (#{Contact.table_name}.id in (#{contacts.any? ? contacts.map(&:id).join(', ') : 'NULL'})"
|
||||
cond << " or #{Deal.table_name}.id in (#{deals.any? ? deals.map(&:id).join(', ') : 'NULL'}))"
|
||||
cond << " and (#{Note.table_name}.content LIKE '%#{params[:search_note]}%')" if params[:search_note] and request.xhr?
|
||||
cond << " and (#{Note.table_name}.author_id = #{params[:note_author_id]})" if !params[:note_author_id].blank?
|
||||
|
||||
|
||||
@notes_pages, @notes = paginate :notes,
|
||||
:per_page => 20,
|
||||
:joins => joins,
|
||||
:conditions => cond,
|
||||
:order => "created_on DESC"
|
||||
@notes.compact!
|
||||
|
||||
|
||||
respond_to do |format|
|
||||
format.html { render :partial => "notes/notes_list", :layout => false, :locals => {:notes => @notes, :notes_pages => @notes_pages} if request.xhr?}
|
||||
format.xml { render :xml => @notes }
|
||||
end
|
||||
end
|
||||
|
||||
def context_menu
|
||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank?
|
||||
@contacts = Contact.visible.all(:conditions => {:id => params[:selected_contacts]})
|
||||
@contact = @contacts.first if (@contacts.size == 1)
|
||||
@can = {:edit => (@contact && @contact.editable?) || (@contacts && @contacts.collect{|c| c.editable?}.inject{|memo,d| memo && d}),
|
||||
:create_deal => (@project && User.current.allowed_to?(:edit_deals, @project)),
|
||||
:delete => @contacts.collect{|c| c.deletable?}.inject{|memo,d| memo && d},
|
||||
:send_mails => @contacts.collect{|c| c.send_mail_allowed? && !c.emails.first.blank?}.inject{|memo,d| memo && d}
|
||||
}
|
||||
|
||||
# @back = back_url
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
def bulk_destroy
|
||||
@contacts = Contact.deletable.find_all_by_id(params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @contacts.empty?
|
||||
@contacts.each(&:destroy)
|
||||
redirect_to :action => "index", :project_id => params[:project_id]
|
||||
end
|
||||
|
||||
def bulk_edit
|
||||
@contacts = Contact.editable.find_all_by_id(params[:ids])
|
||||
@projects = @contacts.collect{|p| p.projects.compact}.compact.flatten.uniq
|
||||
raise ActiveRecord::RecordNotFound if @contacts.empty?
|
||||
@tag_list = ActsAsTaggableOn::TagList.from(@contacts.map(&:tag_list).inject{|memo,t| memo | t})
|
||||
@project = @projects.first
|
||||
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
|
||||
@add_projects = Project.visible.has_module(:contacts_module).find(:all, :order => 'lft')
|
||||
end
|
||||
|
||||
def bulk_update
|
||||
@contacts = Contact.editable.find_all_by_id(params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if @contacts.empty?
|
||||
unsaved_contact_ids = []
|
||||
@contacts.each do |contact|
|
||||
contact.reload
|
||||
params[:contact][:tag_list] = (contact.tag_list + ActsAsTaggableOn::TagList.from(params[:add_tag_list]) - ActsAsTaggableOn::TagList.from(params[:delete_tag_list])).uniq
|
||||
|
||||
add_project_ids = (!params[:add_projects_list].to_s.blank? && params[:add_projects_list].is_a?(Array)) ? Project.find(:all, :conditions => {:id => params[:add_projects_list].collect{|p| p.to_i}}).map(&:id) : []
|
||||
delete_project_ids = (!params[:delete_projects_list].to_s.blank? && params[:delete_projects_list].is_a?(Array)) ? Project.find(:all, :conditions => {:id => params[:delete_projects_list].collect{|p| p.to_i}}).map(&:id) : []
|
||||
project_ids = contact.project_ids + add_project_ids - delete_project_ids
|
||||
params[:contact][:project_ids] = project_ids if project_ids.any?
|
||||
|
||||
contact.tags.clear
|
||||
unless contact.update_attributes(parse_params_for_bulk_contact_attributes(params))
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_contact_ids << contact.id
|
||||
end
|
||||
if !params[:note][:content].blank?
|
||||
note = ContactNote.new(params[:note])
|
||||
note.author = User.current
|
||||
contact.notes << note
|
||||
end
|
||||
|
||||
end
|
||||
set_flash_from_bulk_issue_save(@contacts, unsaved_contact_ids)
|
||||
redirect_back_or_default({:controller => 'contacts', :action => 'index', :project_id => @project})
|
||||
end
|
||||
|
||||
def edit_mails
|
||||
@contacts = Contact.visible.find_all_by_id(params[:ids]).reject{|c| c.email.blank?}
|
||||
raise ActiveRecord::RecordNotFound if @contacts.empty?
|
||||
if !@contacts.collect{|c| c.send_mail_allowed?}.inject{|memo,d| memo && d}
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def send_mails
|
||||
contacts = Contact.visible.find_all_by_id(params[:ids])
|
||||
raise ActiveRecord::RecordNotFound if contacts.empty?
|
||||
if !contacts.collect{|c| c.send_mail_allowed?}.inject{|memo,d| memo && d}
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
raise_delivery_errors = ActionMailer::Base.raise_delivery_errors
|
||||
# Force ActionMailer to raise delivery errors so we can catch it
|
||||
ActionMailer::Base.raise_delivery_errors = true
|
||||
delivered_contacts = []
|
||||
error_contacts = []
|
||||
contacts.each do |contact|
|
||||
begin
|
||||
params[:message] = mail_macro(contact, params[:"message-content"])
|
||||
|
||||
ContactsMailer.deliver_bulk_mail(contact, params)
|
||||
delivered_contacts << contact
|
||||
|
||||
note = ContactNote.new
|
||||
note.subject = params[:subject]
|
||||
note.content = params[:message]
|
||||
note.author = User.current
|
||||
note.type_id = Note.note_types[:email]
|
||||
contact.notes << note
|
||||
Attachment.attach_files(note, params[:attachments])
|
||||
render_attachment_warning_if_needed(note)
|
||||
|
||||
rescue Exception => e
|
||||
error_contacts << [contact, e.message]
|
||||
end
|
||||
flash[:notice] = l(:notice_email_sent, delivered_contacts.map{|c| "#{c.name} (#{c.emails.first})"}.join(', ')) if delivered_contacts.any?
|
||||
flash[:error] = l(:notice_email_error, error_contacts.map{|e| "#{e[0].name}: #{e[1]}"}.join(', ')) if error_contacts.any?
|
||||
end
|
||||
|
||||
ActionMailer::Base.raise_delivery_errors = raise_delivery_errors
|
||||
redirect_back_or_default({:controller => 'contacts', :action => 'index', :project_id => params[:project_id]})
|
||||
end
|
||||
|
||||
def preview_email
|
||||
@text = mail_macro(Contact.visible.first(:conditions => {:id => params[:ids][0]}), params[:"message-content"])
|
||||
render :partial => 'common/preview'
|
||||
end
|
||||
|
||||
|
||||
|
||||
private
|
||||
def attach_avatar
|
||||
if params[:contact_avatar]
|
||||
params[:contact_avatar][:description] = 'avatar'
|
||||
@contact.avatar.destroy if @contact.avatar
|
||||
Attachment.attach_files(@contact, {"1" => params[:contact_avatar]})
|
||||
render_attachment_warning_if_needed(@contact)
|
||||
end
|
||||
end
|
||||
|
||||
def last_notes(count=5)
|
||||
# @last_notes = find_contacts(false).find(:all, :include => :notes, :limit => count, :order => 'notes.created_on DESC').map{|c| c.notes}.flatten.first(count)
|
||||
scope = ContactNote.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Project.table_name}.id = ?", @project.id]) if @project
|
||||
|
||||
@last_notes = scope.visible.find(:all,
|
||||
:limit => count,
|
||||
:order => "#{ContactNote.table_name}.created_on DESC")
|
||||
# @last_notes = []
|
||||
end
|
||||
|
||||
def find_contact
|
||||
@contact = Contact.find(params[:id])
|
||||
@project = (@contact.projects.visible.find(params[:project_id]) rescue false) if params[:project_id]
|
||||
@project ||= @contact.project
|
||||
# if !(params[:project_id] == @project.identifier)
|
||||
# params[:project_id] = @project.identifier
|
||||
# redirect_to params
|
||||
# end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_tags
|
||||
scope = ActsAsTaggableOn::Tag.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Project.table_name}.id = ?", @project.id]) if @project
|
||||
scope = scope.scoped(:conditions => [Contact.allowed_to_condition(User.current, :view_contacts)])
|
||||
|
||||
joins = []
|
||||
joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.id "
|
||||
joins << "JOIN #{Contact.table_name} ON #{Contact.table_name}.id = #{ActsAsTaggableOn::Tagging.table_name}.taggable_id AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = '#{Contact.name}' "
|
||||
joins << Contact.projects_joins
|
||||
|
||||
options = {}
|
||||
options[:select] = "#{ActsAsTaggableOn::Tag.table_name}.*, COUNT(DISTINCT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id) AS count"
|
||||
# options[:conditions] = cond.conditions
|
||||
options[:joins] = joins.flatten
|
||||
options[:group] = "#{ActsAsTaggableOn::Tag.table_name}.id, #{ActsAsTaggableOn::Tag.table_name}.name, #{ActsAsTaggableOn::Tag.table_name}.created_at, #{ActsAsTaggableOn::Tag.table_name}.updated_at, #{ActsAsTaggableOn::Tag.table_name}.color"
|
||||
options[:order] = "#{ActsAsTaggableOn::Tag.table_name}.name"
|
||||
|
||||
@tags = scope.find(:all, options)
|
||||
end
|
||||
|
||||
def find_deals
|
||||
scope = Deal.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.project_id = ?", @project.id]) if @project
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.name LIKE ? ", "%" + params[:search] + "%"]) if params[:search]
|
||||
scope = scope.scoped(:conditions => ["1=0"]) if params[:tag]
|
||||
@deals = scope.visible.find(:all) || []
|
||||
end
|
||||
|
||||
def find_contacts(pages=true)
|
||||
@tag = ActsAsTaggableOn::TagList.from(params[:tag]).map{|tag| ActsAsTaggableOn::Tag.find_by_name(tag) } unless params[:tag].blank?
|
||||
|
||||
scope = Contact.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Contact.table_name}.job_title = ?", params[:job_title]]) unless params[:job_title].blank?
|
||||
scope = scope.scoped(:conditions => ["#{Contact.table_name}.assigned_to_id = ?", params[:assigned_to_id]]) unless params[:assigned_to_id].blank?
|
||||
scope = scope.scoped(:conditions => ["#{Contact.table_name}.is_company = ?", params[:query]]) unless (params[:query].blank? || params[:query] == '2' || params[:query] == '3')
|
||||
scope = scope.scoped(:conditions => ["#{Contact.table_name}.author_id = ?", User.current]) if params[:query] == '3'
|
||||
|
||||
case params[:query]
|
||||
when '2' then scope = scope.order_by_creation
|
||||
when '3' then scope = scope.order_by_creation
|
||||
else scope = scope.order_by_name
|
||||
end
|
||||
|
||||
scope = scope.in_project(@project.id) if @project
|
||||
params[:search].split(' ').collect{ |search_string| scope = scope.live_search(search_string) } if !params[:search].blank?
|
||||
scope = scope.visible
|
||||
|
||||
scope = scope.tagged_with(params[:tag]) if !params[:tag].blank?
|
||||
|
||||
|
||||
@contacts_count = scope.count
|
||||
@contacts = scope
|
||||
|
||||
if pages
|
||||
page_size = params[:page_size].blank? ? 20 : params[:page_size].to_i
|
||||
@contacts_pages = Paginator.new(self, @contacts_count, page_size, params[:page])
|
||||
@offset = @contacts_pages.current.offset
|
||||
@limit = @contacts_pages.items_per_page
|
||||
|
||||
@contacts = @contacts.scoped :include => [:tags, :avatar], :limit => @limit, :offset => @offset
|
||||
|
||||
fake_name = @contacts.first.name if @contacts.length > 0
|
||||
end
|
||||
@contacts
|
||||
|
||||
end
|
||||
|
||||
# Filter for bulk issue operations
|
||||
def bulk_find_contacts
|
||||
@contacts = Deal.find_all_by_id(params[:id] || params[:ids], :include => :project)
|
||||
raise ActiveRecord::RecordNotFound if @contact.empty?
|
||||
if @contacts.detect {|contact| !contact.visible?}
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@projects = @contacts.collect(&:projects).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
|
||||
def parse_params_for_bulk_contact_attributes(params)
|
||||
attributes = (params[:contact] || {}).reject {|k,v| v.blank?}
|
||||
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
|
||||
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
|
||||
attributes
|
||||
end
|
||||
|
||||
def mail_macro(contact, message)
|
||||
message = message.gsub(/%%NAME%%/, contact.first_name)
|
||||
message = message.gsub(/%%FULL_NAME%%/, contact.name)
|
||||
message = message.gsub(/%%COMPANY%%/, contact.company) if contact.company
|
||||
message = message.gsub(/%%LAST_NAME%%/, contact.last_name) if contact.last_name
|
||||
message = message.gsub(/%%MIDDLE_NAME%%/, contact.middle_name) if contact.middle_name
|
||||
message = message.gsub(/%%DATE%%/, Date.today.to_s)
|
||||
|
||||
contact.custom_field_values.each do |value|
|
||||
message = message.gsub(/%%#{value.custom_field.name}%%/, value.value.to_s)
|
||||
end
|
||||
message
|
||||
end
|
||||
|
||||
def find_project
|
||||
project_id = (params[:contact] && params[:contact][:project_id]) || params[:project_id]
|
||||
@project = Project.find(project_id)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
52
app/controllers/contacts_duplicates_controller.rb
Normal file
52
app/controllers/contacts_duplicates_controller.rb
Normal file
@ -0,0 +1,52 @@
|
||||
class ContactsDuplicatesController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize
|
||||
before_filter :find_contact, :except => :duplicates
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def duplicates
|
||||
search_first_name = params[:contact][:first_name] if params[:contact] && !params[:contact][:first_name].blank?
|
||||
search_last_name = params[:contact][:last_name] if params[:contact] && !params[:contact][:last_name].blank?
|
||||
search_middle_name = params[:contact][:middle_name] if params[:contact] && !params[:contact][:middle_name].blank?
|
||||
|
||||
@contact = (Contact.find(params[:contact_id]) if !params[:contact_id].blank?) || Contact.new
|
||||
@contact.first_name = search_first_name || ""
|
||||
@contact.last_name = search_last_name || ""
|
||||
@contact.middle_name = search_middle_name || ""
|
||||
respond_to do |format|
|
||||
format.html {render :partial => "duplicates", :layout => false if request.xhr?}
|
||||
end
|
||||
end
|
||||
|
||||
def merge
|
||||
@dublicate = Contact.find(params[:dublicate_id])
|
||||
@dublicate.notes << @contact.notes
|
||||
@dublicate.deals << @contact.deals
|
||||
@dublicate.issues << @contact.issues
|
||||
@dublicate.projects << @contact.projects
|
||||
@dublicate.email = (@dublicate.emails | @contact.emails).join(', ')
|
||||
@dublicate.phone = (@dublicate.phones | @contact.phones).join(', ')
|
||||
|
||||
@dublicate.tag_list = @dublicate.tag_list | @contact.tag_list
|
||||
if @dublicate.save && @contact.destroy
|
||||
flash[:notice] = l(:notice_successful_merged)
|
||||
redirect_to :controller => "contacts", :action => "show", :project_id => @project, :id => @dublicate
|
||||
else
|
||||
render "index"
|
||||
end
|
||||
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
redirect_to :controller => "contacts", :action => "show", :project_id => @project, :id => @contact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_contact
|
||||
@contact = Contact.find(params[:contact_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404 if !request.xhr?
|
||||
end
|
||||
end
|
||||
27
app/controllers/contacts_mailer_controller.rb
Normal file
27
app/controllers/contacts_mailer_controller.rb
Normal file
@ -0,0 +1,27 @@
|
||||
class ContactsMailerController < ActionController::Base
|
||||
before_filter :check_credential
|
||||
|
||||
verify :method => :post,
|
||||
:only => :index,
|
||||
:render => { :nothing => true, :status => 405 }
|
||||
|
||||
# Submits an incoming email to ContactsMailer
|
||||
def index
|
||||
options = params.dup
|
||||
email = options.delete(:email)
|
||||
if ContactsMailer.receive(email, options)
|
||||
render :nothing => true, :status => :created
|
||||
else
|
||||
render :nothing => true, :status => :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_credential
|
||||
User.current = nil
|
||||
unless Setting.mail_handler_api_enabled? && params[:key].to_s == Setting.mail_handler_api_key
|
||||
render :text => 'Access denied. Incoming emails WS is disabled or key is invalid.', :status => 403
|
||||
end
|
||||
end
|
||||
end
|
||||
52
app/controllers/contacts_projects_controller.rb
Normal file
52
app/controllers/contacts_projects_controller.rb
Normal file
@ -0,0 +1,52 @@
|
||||
class ContactsProjectsController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize
|
||||
before_filter :find_contact
|
||||
before_filter :check_count, :only => :delete
|
||||
|
||||
def add
|
||||
@show_form = "true"
|
||||
# find_contact
|
||||
if params[:new_project_id] then
|
||||
project = Project.has_module(:contacts_module).find(params[:new_project_id])
|
||||
@contact.projects << project
|
||||
@contact.save if request.post?
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'contact_projects', :partial => 'related'
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue ::ActionController::RedirectBackError
|
||||
render :text => 'Project added.', :layout => true
|
||||
end
|
||||
|
||||
def delete
|
||||
@contact.projects.delete(Project.find(params[:disconnect_project_id])) if request.post?
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'contact_projects', :partial => 'related'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_count
|
||||
deny_access if @contact.projects.size <= 1
|
||||
end
|
||||
|
||||
def find_contact
|
||||
@contact = Contact.find(params[:contact_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
end
|
||||
15
app/controllers/contacts_settings_controller.rb
Normal file
15
app/controllers/contacts_settings_controller.rb
Normal file
@ -0,0 +1,15 @@
|
||||
class ContactsSettingsController < ApplicationController
|
||||
unloadable
|
||||
before_filter :find_project_by_project_id, :authorize
|
||||
|
||||
def save
|
||||
if params[:contacts_settings] && params[:contacts_settings].is_a?(Hash) then
|
||||
settings = params[:contacts_settings]
|
||||
settings.map do |k, v|
|
||||
ContactsSetting[k, @project.id] = v
|
||||
end
|
||||
end
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'contacts', :id => @project
|
||||
end
|
||||
|
||||
end
|
||||
48
app/controllers/contacts_tags_controller.rb
Normal file
48
app/controllers/contacts_tags_controller.rb
Normal file
@ -0,0 +1,48 @@
|
||||
class ContactsTagsController < ApplicationController
|
||||
unloadable
|
||||
before_filter :require_admin
|
||||
before_filter :find_tag
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @tag.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
else
|
||||
flash[:error] = l(:notice_unsuccessful_delete)
|
||||
end
|
||||
redirect_to :back
|
||||
|
||||
end
|
||||
|
||||
|
||||
def update
|
||||
@tag.color_name = params[:tag][:color_name]
|
||||
@tag.name = params[:tag][:name]
|
||||
if @tag.save
|
||||
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :controller => 'settings', :action => 'plugin', :id => 'contacts', :tab => "tags"}
|
||||
format.xml { }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render :action => "edit"}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_tag
|
||||
@tag = ActsAsTaggableOn::Tag.find(params[:id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
117
app/controllers/contacts_tasks_controller.rb
Normal file
117
app/controllers/contacts_tasks_controller.rb
Normal file
@ -0,0 +1,117 @@
|
||||
class ContactsTasksController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => :index
|
||||
before_filter :find_contact, :except => [:index, :add, :close]
|
||||
before_filter :find_issue, :except => [:index, :new]
|
||||
|
||||
def index
|
||||
cond = "(1=1)"
|
||||
# cond = "issues.assigned_to_id = #{User.current.id}"
|
||||
cond << " and issues.project_id = #{@project.id}" if @project
|
||||
cond << " and (issues.assigned_to_id = #{params[:assigned_to]})" unless params[:assigned_to].blank?
|
||||
|
||||
@contacts_issues = Issue.visible.find(:all,
|
||||
:joins => "INNER JOIN contacts_issues ON issues.id = contacts_issues.issue_id",
|
||||
# :group => :issue_id,
|
||||
:conditions => cond,
|
||||
:order => "issues.due_date")
|
||||
@users = assigned_to_users
|
||||
end
|
||||
|
||||
def new
|
||||
issue = Issue.new
|
||||
issue.project = @project
|
||||
issue.author = User.current
|
||||
issue.status = IssueStatus.default
|
||||
issue.start_date ||= Date.today
|
||||
issue.contacts << @contact
|
||||
issue.safe_attributes = params[:issue] if params[:issue]
|
||||
|
||||
if issue.save
|
||||
flash[:notice] = l(:notice_successful_add)
|
||||
redirect_to :back
|
||||
else
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add
|
||||
@show_form = "true"
|
||||
|
||||
if params[:contact_id] && request.post? then
|
||||
find_contact
|
||||
@contact.issues << @issue
|
||||
@contact.save
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'issue_contacts', :partial => 'issues/contacts'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
@issue.contacts.delete(@contact)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'issue_contacts', :partial => 'issues/contacts'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
@issue.status = IssueStatus.find(:first, :conditions => { :is_closed => true })
|
||||
@issue.save
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page["issue_#{params[:issue_id]}"].visual_effect :fade
|
||||
end
|
||||
end
|
||||
format.html {redirect_to :back }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_contact
|
||||
@contact = Contact.find(params[:contact_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_issue
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def assigned_to_users
|
||||
user_values = []
|
||||
project = @project
|
||||
user_values << ["<< #{l(:label_all)} >>", ""]
|
||||
user_values << ["<< #{l(:label_me)} >>", User.current.id] if User.current.logged?
|
||||
if project
|
||||
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
|
||||
else
|
||||
project_ids = Project.all(:conditions => Project.visible_condition(User.current)).collect(&:id)
|
||||
if project_ids.any?
|
||||
# members of the user's projects
|
||||
user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
36
app/controllers/contacts_vcf_controller.rb
Normal file
36
app/controllers/contacts_vcf_controller.rb
Normal file
@ -0,0 +1,36 @@
|
||||
class ContactsVcfController < ApplicationController
|
||||
unloadable
|
||||
|
||||
require 'vpim/vcard'
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize
|
||||
|
||||
def load
|
||||
begin
|
||||
vcard = Vpim::Vcard.decode(params[:contact_vcf]).first
|
||||
contact = {}
|
||||
contact[:first_name] = vcard.name.given
|
||||
contact[:middle_name] = vcard.name.additional
|
||||
contact[:last_name] = vcard.name.family
|
||||
contact[:phone] = vcard.telephones.join(', ')
|
||||
contact[:email] = vcard.emails.join(', ')
|
||||
contact[:website] = vcard.url.uri if vcard.url
|
||||
contact[:address] = vcard['ADR'].gsub('\\n', "\n") if vcard['ADR']
|
||||
contact[:birthday] = vcard.birthday
|
||||
contact[:background] = vcard.note
|
||||
contact[:company] = vcard.org.first if vcard.org
|
||||
contact[:job_title] = vcard.title
|
||||
|
||||
respond_to do |format|
|
||||
format.html{ redirect_to :controller => "contacts", :action => "new", :project_id => @project, :contact => contact }
|
||||
end
|
||||
|
||||
rescue Exception => e
|
||||
flash[:error] = e.message
|
||||
respond_to do |format|
|
||||
format.html{ redirect_to :back }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
68
app/controllers/deal_categories_controller.rb
Normal file
68
app/controllers/deal_categories_controller.rb
Normal file
@ -0,0 +1,68 @@
|
||||
class DealCategoriesController < ApplicationController
|
||||
unloadable
|
||||
menu_item :settings
|
||||
model_object DealCategory
|
||||
before_filter :find_model_object, :except => :new
|
||||
before_filter :find_project_from_association, :except => :new
|
||||
before_filter :find_project_by_project_id, :only => :new
|
||||
before_filter :authorize
|
||||
|
||||
verify :method => :post, :only => :destroy
|
||||
|
||||
def new
|
||||
@category = @project.deal_categories.build(params[:category])
|
||||
if request.post?
|
||||
if @category.save
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'contacts', :id => @project
|
||||
end
|
||||
format.js do
|
||||
# IE doesn't support the replace_html rjs method for select box options
|
||||
render(:update) {|page| page.replace "deal_category_id",
|
||||
content_tag('select', '<option></option>' + options_from_collection_for_select(@project.deal_categories, 'id', 'name', @category.id), :id => 'deal_category_id', :name => 'deal[category_id]')
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js do
|
||||
render(:update) {|page| page.alert(@category.errors.full_messages.join('\n')) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
if request.post? and @category.update_attributes(params[:category])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'contacts', :id => @project
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@deal_count = @category.deals.size
|
||||
if @deal_count == 0
|
||||
# No deal assigned to this category
|
||||
@category.destroy
|
||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'contacts'
|
||||
elsif params[:todo]
|
||||
reassign_to = @project.deal_categories.find_by_id(params[:reassign_to_id]) if params[:todo] == 'reassign'
|
||||
@category.destroy(reassign_to)
|
||||
redirect_to :controller => 'projects', :action => 'settings', :id => @project, :tab => 'contacts'
|
||||
end
|
||||
@categories = @project.deal_categories - [@category]
|
||||
end
|
||||
|
||||
private
|
||||
# Wrap ApplicationController's find_model_object method to set
|
||||
# @category instead of just @deal_category
|
||||
def find_model_object
|
||||
super
|
||||
@category = @object
|
||||
@project = @category.project
|
||||
end
|
||||
end
|
||||
56
app/controllers/deal_contacts_controller.rb
Normal file
56
app/controllers/deal_contacts_controller.rb
Normal file
@ -0,0 +1,56 @@
|
||||
class DealContactsController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize
|
||||
before_filter :find_contact, :only => :delete
|
||||
before_filter :find_deal
|
||||
|
||||
helper :deals
|
||||
|
||||
def add
|
||||
@show_form = "true"
|
||||
|
||||
if params[:contact_id] && request.post? then
|
||||
find_contact
|
||||
if !@deal.all_contacts.include?(@contact)
|
||||
@deal.related_contacts << @contact
|
||||
@deal.save
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'deal_contacts', :partial => 'deal_contacts/contacts'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
@deal.related_contacts.delete(@contact)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'deal_contacts', :partial => 'deal_contacts/contacts'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
def find_contact
|
||||
@contact = Contact.find(params[:contact_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_deal
|
||||
@deal = Deal.find(params[:deal_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
end
|
||||
63
app/controllers/deal_statuses_controller.rb
Normal file
63
app/controllers/deal_statuses_controller.rb
Normal file
@ -0,0 +1,63 @@
|
||||
class DealStatusesController < ApplicationController
|
||||
unloadable
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_filter :require_admin, :except => :assing_to_project
|
||||
before_filter :find_project_by_project_id, :authorize, :only => :assing_to_project
|
||||
|
||||
verify :method => :post, :only => [ :destroy, :create, :update, :move ],
|
||||
:redirect_to => { :action => :index }
|
||||
|
||||
def index
|
||||
@deal_status_pages, @deal_statuses = paginate :deal_statuses, :per_page => 25, :order => "position"
|
||||
render :action => "index", :layout => false if request.xhr?
|
||||
end
|
||||
|
||||
def new
|
||||
@deal_status = DealStatus.new
|
||||
end
|
||||
|
||||
def create
|
||||
@deal_status = DealStatus.new(params[:deal_status])
|
||||
if @deal_status.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action =>"plugin", :id => "contacts", :controller => "settings", :tab => 'deal_statuses'
|
||||
else
|
||||
render :action => 'new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@deal_status = DealStatus.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@deal_status = DealStatus.find(params[:id])
|
||||
if @deal_status.update_attributes(params[:deal_status])
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action =>"plugin", :id => "contacts", :controller => "settings", :tab => 'deal_statuses'
|
||||
else
|
||||
render :action => 'edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
DealStatus.find(params[:id]).destroy
|
||||
redirect_to :action =>"plugin", :id => "contacts", :controller => "settings", :tab => 'deal_statuses'
|
||||
rescue
|
||||
flash[:error] = l(:error_unable_delete)
|
||||
redirect_to :action =>"plugin", :id => "contacts", :controller => "settings", :tab => 'deal_statuses'
|
||||
end
|
||||
|
||||
def assing_to_project
|
||||
|
||||
if request.put?
|
||||
@project.deal_statuses = !params[:deal_statuses].blank? ? DealStatus.find(params[:deal_statuses]) : []
|
||||
@project.save
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
end
|
||||
redirect_to :controller => 'projects', :action => 'settings', :tab => 'deals', :id => @project
|
||||
end
|
||||
|
||||
end
|
||||
247
app/controllers/deals_controller.rb
Normal file
247
app/controllers/deals_controller.rb
Normal file
@ -0,0 +1,247 @@
|
||||
class DealsController < ApplicationController
|
||||
unloadable
|
||||
|
||||
PRICE_TYPE_PULLDOWN = [l(:label_price_fixed_bid), l(:label_price_per_hour)]
|
||||
|
||||
before_filter :find_deal, :only => [:show, :edit, :update, :destroy]
|
||||
before_filter :find_project_by_project_id, :only => [:new, :create]
|
||||
before_filter :bulk_find_deals, :only => [:bulk_update, :bulk_edit, :bulk_destroy, :context_menu]
|
||||
before_filter :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => [:index]
|
||||
# before_filter :find_deals, :only => :index
|
||||
before_filter :update_deal_from_params, :only => [:edit, :update]
|
||||
before_filter :build_new_deal_from_params, :only => [:new, :create]
|
||||
before_filter :find_deal_attachments, :only => :show
|
||||
|
||||
helper :attachments
|
||||
helper :contacts
|
||||
helper :notes
|
||||
helper :timelog
|
||||
helper :watchers
|
||||
helper :custom_fields
|
||||
include WatchersHelper
|
||||
include DealsHelper
|
||||
|
||||
def new
|
||||
@deal = Deal.new
|
||||
@deal.contact = Contact.find(params[:contact_id]) if params[:contact_id]
|
||||
@deal.assigned_to = User.current
|
||||
end
|
||||
|
||||
def create
|
||||
@deal = Deal.new(params[:deal])
|
||||
# @deal.contacts = [Contact.find(params[:contacts])]
|
||||
@deal.project = @project
|
||||
@deal.author = User.current
|
||||
@deal.init_deal_process(User.current)
|
||||
if @deal.save
|
||||
flash[:notice] = l(:notice_successful_create)
|
||||
redirect_to :action => "show", :id => @deal
|
||||
else
|
||||
render :action => "new"
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@deal.init_deal_process(User.current)
|
||||
if @deal.update_attributes(params[:deal])
|
||||
# @deal.contacts = [Contact.find(params[:contacts])] if params[:contacts]
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :action => "show", :id => @deal }
|
||||
format.xml { }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
format.html { render :action => "edit"}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def edit
|
||||
respond_to do |format|
|
||||
format.html { }
|
||||
format.xml { }
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
retrieve_deals_query
|
||||
params[:status_id] = "o" unless params.has_key?(:status_id)
|
||||
find_deals
|
||||
respond_to do |format|
|
||||
format.html{ request.xhr? ? render( :partial => "list", :layout => false, :locals => {:deals => @deals}) : last_notes }
|
||||
format.xml { render :xml => @deals}
|
||||
format.json { render :text => @deals.to_json, :layout => false }
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@note = DealNote.new
|
||||
respond_to do |format|
|
||||
format.html { @deal.viewed }
|
||||
format.xml { }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if @deal.destroy
|
||||
flash[:notice] = l(:notice_successful_delete)
|
||||
else
|
||||
flash[:error] = l(:notice_unsuccessful_save)
|
||||
end
|
||||
redirect_to :action => "index", :project_id => params[:project_id]
|
||||
|
||||
end
|
||||
|
||||
def context_menu
|
||||
@deal = @deals.first if (@deals.size == 1)
|
||||
@can = {:edit => User.current.allowed_to?(:edit_deals, @projects),
|
||||
:delete => User.current.allowed_to?(:delete_deals, @projects)
|
||||
}
|
||||
|
||||
# @back = back_url
|
||||
render :layout => false
|
||||
end
|
||||
|
||||
def bulk_destroy
|
||||
@deals.each do |deal|
|
||||
begin
|
||||
deal.reload.destroy
|
||||
rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
|
||||
# nothing to do, issue was already deleted (eg. by a parent)
|
||||
end
|
||||
end
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
|
||||
format.api { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
def bulk_edit
|
||||
@available_statuses = @projects.map(&:deal_statuses).inject{|memo,w| memo & w}
|
||||
@available_categories = @projects.map(&:deal_categories).inject{|memo,w| memo & w}
|
||||
@assignables = @projects.map(&:assignable_users).inject{|memo,a| memo & a}
|
||||
end
|
||||
|
||||
|
||||
def bulk_update
|
||||
unsaved_deal_ids = []
|
||||
@deals.each do |deal|
|
||||
deal.reload
|
||||
deal.init_deal_process(User.current)
|
||||
unless deal.update_attributes(parse_params_for_bulk_deal_attributes(params))
|
||||
# Keep unsaved issue ids to display them in flash error
|
||||
unsaved_deal_ids << deal.id
|
||||
end
|
||||
if params[:note] && !params[:note][:content].blank?
|
||||
note = DealNote.new(params[:note])
|
||||
note.author = User.current
|
||||
deal.notes << note
|
||||
end
|
||||
|
||||
end
|
||||
set_flash_from_bulk_issue_save(@deals, unsaved_deal_ids)
|
||||
redirect_back_or_default({:controller => 'deals', :action => 'index', :project_id => @project})
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def last_notes(count=5)
|
||||
# TODO: Исправить говнокод этот и выделить все в плагин acts-as-noteble
|
||||
scope = DealNote.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.project_id = ?", @project.id]) if @project
|
||||
|
||||
@last_notes = scope.visible.find(:all,
|
||||
:limit => count,
|
||||
:order => "#{DealNote.table_name}.created_on DESC")
|
||||
end
|
||||
|
||||
|
||||
def build_new_deal_from_params
|
||||
end
|
||||
|
||||
def update_deal_from_params
|
||||
end
|
||||
|
||||
def find_deal_attachments
|
||||
@deal_attachments = Attachment.find(:all,
|
||||
:conditions => { :container_type => "Note", :container_id => @deal.notes.map(&:id)},
|
||||
:order => "created_on DESC")
|
||||
end
|
||||
|
||||
|
||||
def find_deals(pages=true)
|
||||
retrieve_date_range(params[:period].to_s)
|
||||
|
||||
scope = Deal.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.project_id = ?", @project.id]) if @project
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.status_id = ?", params[:status_id]]) if (!params[:status_id].blank? && params[:status_id] != "o")
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.category_id = ?", params[:category_id]]) if !params[:category_id].blank?
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.assigned_to_id = ?", params[:assigned_to_id]]) if !params[:assigned_to_id].blank?
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.created_on BETWEEN ? AND ?", @from, @to]) if (@from && @to)
|
||||
|
||||
params[:search].split(' ').collect{ |search_string| scope = scope.live_search(search_string) } if !params[:search].blank?
|
||||
scope = scope.visible
|
||||
scope = scope.scoped(:include => :status, :conditions => ["#{DealStatus.table_name}.is_closed = ?", false]) if (params[:status_id] == "o")
|
||||
scope = scope.scoped(:order => :status_id)
|
||||
|
||||
|
||||
@deals_count = scope.count
|
||||
|
||||
if pages
|
||||
@deals_sum = scope.sum(:price, :group => :currency)
|
||||
|
||||
page_size = params[:page_size].blank? ? 20 : params[:page_size].to_i
|
||||
@deals_pages = Paginator.new(self, @deals_count, page_size, params[:page])
|
||||
@offset = @deals_pages.current.offset
|
||||
@limit = @deals_pages.items_per_page
|
||||
|
||||
scope = scope.scoped :limit => @limit, :offset => @offset
|
||||
@deals = scope
|
||||
|
||||
fake_name = @deals.first.price if @deals.length > 0 #without this patch paging does not work
|
||||
end
|
||||
|
||||
scope
|
||||
|
||||
end
|
||||
|
||||
# Filter for bulk issue operations
|
||||
def bulk_find_deals
|
||||
@deals = Deal.find_all_by_id(params[:id] || params[:ids], :include => :project)
|
||||
raise ActiveRecord::RecordNotFound if @deals.empty?
|
||||
if @deals.detect {|deal| !deal.visible?}
|
||||
deny_access
|
||||
return
|
||||
end
|
||||
@projects = @deals.collect(&:project).compact.uniq
|
||||
@project = @projects.first if @projects.size == 1
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
|
||||
def find_deal
|
||||
@deal = Deal.find(params[:id], :include => [:project, :status, :category])
|
||||
@project ||= @deal.project
|
||||
# if !(params[:project_id] == @project.identifier)
|
||||
# params[:project_id] = @project.identifier
|
||||
# redirect_to params
|
||||
# end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def parse_params_for_bulk_deal_attributes(params)
|
||||
attributes = (params[:deal] || {}).reject {|k,v| v.blank?}
|
||||
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
|
||||
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
|
||||
attributes
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
121
app/controllers/deals_tasks_controller.rb
Normal file
121
app/controllers/deals_tasks_controller.rb
Normal file
@ -0,0 +1,121 @@
|
||||
class DealsTasksController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => :index
|
||||
before_filter :find_deal, :except => [:index, :add, :close]
|
||||
before_filter :find_issue, :except => [:index, :new]
|
||||
|
||||
def index
|
||||
cond = "(1=1)"
|
||||
# cond = "issues.assigned_to_id = #{User.current.id}"
|
||||
cond << " and issues.project_id = #{@project.id}" if @project
|
||||
cond << " and (issues.assigned_to_id = #{params[:assigned_to]})" unless params[:assigned_to].blank?
|
||||
|
||||
@deals_issues = Issue.visible.find(:all,
|
||||
:joins => "INNER JOIN deals_issues ON issues.id = deals_issues.issue_id",
|
||||
# :group => :issue_id,
|
||||
:conditions => cond,
|
||||
:order => "issues.due_date")
|
||||
@users = assigned_to_users
|
||||
end
|
||||
|
||||
def new
|
||||
issue = Issue.new
|
||||
issue.subject = params[:task_subject]
|
||||
issue.project = @project
|
||||
issue.tracker_id = params[:task_tracker]
|
||||
issue.author = User.current
|
||||
issue.due_date = params[:due_date]
|
||||
issue.assigned_to_id = params[:assigned_to]
|
||||
issue.description = params[:task_description]
|
||||
issue.status = IssueStatus.default
|
||||
if issue.save
|
||||
flash[:notice] = l(:notice_successful_add)
|
||||
@deal.issues << issue
|
||||
@deal.save
|
||||
redirect_to :back
|
||||
return
|
||||
else
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add
|
||||
@show_form = "true"
|
||||
|
||||
if params[:deal_id] && request.post? then
|
||||
find_deal
|
||||
@deal.issues << @issue
|
||||
@deal.save
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'issue_deals', :partial => 'issues/deals'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
@issue.deals.delete(@deal)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html 'issue_deals', :partial => 'issues/deals'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
@issue.status = IssueStatus.find(:first, :conditions => { :is_closed => true })
|
||||
@issue.save
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page["issue_#{params[:issue_id]}"].visual_effect :fade
|
||||
end
|
||||
end
|
||||
format.html {redirect_to :back }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_deal
|
||||
@deal = Deal.find(params[:deal_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_issue
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def assigned_to_users
|
||||
user_values = []
|
||||
project = @project
|
||||
user_values << ["<< #{l(:label_all)} >>", ""]
|
||||
user_values << ["<< #{l(:label_me)} >>", User.current.id] if User.current.logged?
|
||||
if project
|
||||
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
|
||||
else
|
||||
project_ids = Project.all(:conditions => Project.visible_condition(User.current)).collect(&:id)
|
||||
if project_ids.any?
|
||||
# members of the user's projects
|
||||
user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
97
app/controllers/notes_controller.rb
Normal file
97
app/controllers/notes_controller.rb
Normal file
@ -0,0 +1,97 @@
|
||||
class NotesController < ApplicationController
|
||||
unloadable
|
||||
default_search_scope :notes
|
||||
# before_filter :find_model_object
|
||||
before_filter :find_project_by_project_id, :except => :show
|
||||
before_filter :find_optional_project, :only => :show
|
||||
before_filter :find_note, :only => [:show, :edit, :update, :destroy]
|
||||
before_filter :authorize, :except => [:index]
|
||||
|
||||
helper :attachments
|
||||
helper :notes
|
||||
helper :custom_fields
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html { }
|
||||
format.xml { }
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def edit
|
||||
(render_403; return false) unless @note.editable_by?(User.current, @project)
|
||||
end
|
||||
|
||||
def update
|
||||
if @note.update_attributes(params[:note])
|
||||
Attachment.attach_files(@note, params[:note_attachments])
|
||||
render_attachment_warning_if_needed(@note)
|
||||
flash[:notice] = l(:notice_successful_update)
|
||||
redirect_to :action => "show", :project_id => @note.source.project, :note_id => @note
|
||||
else
|
||||
render "edit", :project_id => params[:project_id], :note_id => @note
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def add_note
|
||||
find_note_source
|
||||
@note = @note_source.notes.new(params[:note])
|
||||
@note.author = User.current
|
||||
@note.created_on = @note.created_on + Time.now.hour.hours + Time.now.min.minutes + Time.now.sec.seconds if @note.created_on
|
||||
if @note.save
|
||||
Attachment.attach_files(@note, params[:note_attachments])
|
||||
render_attachment_warning_if_needed(@note)
|
||||
flash[:notice] = l(:label_note_added)
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page[:add_note_form].reset
|
||||
page.insert_html :top, "notes", :partial => 'notes/note_item', :object => @note, :locals => {:note_source => @note_source}
|
||||
page["note_#{@note.id}"].visual_effect :highlight
|
||||
flash.discard
|
||||
end
|
||||
end if request.xhr?
|
||||
format.html {redirect_to :back}
|
||||
end
|
||||
else
|
||||
# TODO При render если коммент не добавился то тут появялется ошибка из-за того что не передаются данные для paginate
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
(render_403; return false) unless @note.destroyable_by?(User.current, @project)
|
||||
@note.destroy
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page["note_#{params[:note_id]}"].visual_effect :fade
|
||||
end
|
||||
end if request.xhr?
|
||||
format.html {redirect_to :action => 'show', :project_id => @project, :id => @note.source }
|
||||
end
|
||||
|
||||
# redirect_to :action => 'show', :project_id => @project, :id => @contact
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def find_note
|
||||
@note = Note.find(params[:note_id])
|
||||
@project ||= @note.project
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_note_source
|
||||
klass = Object.const_get(params[:source_type].camelcase)
|
||||
# return false unless klass.respond_to?('watched_by')
|
||||
@note_source = klass.find(params[:source_id])
|
||||
end
|
||||
|
||||
end
|
||||
48
app/controllers/sale_funel_controller.rb
Normal file
48
app/controllers/sale_funel_controller.rb
Normal file
@ -0,0 +1,48 @@
|
||||
class SaleFunelController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_optional_project
|
||||
|
||||
helper :timelog
|
||||
helper :deals
|
||||
include DealsHelper
|
||||
|
||||
def index
|
||||
@sale_funel = []
|
||||
deal_statuses.each do |status|
|
||||
retrieve_date_range(params[:period])
|
||||
scope = DealProcess.scoped({})
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.project_id = ?", @project.id]) if @project
|
||||
scope = scope.scoped(:conditions => ["#{Deal.table_name}.category_id = ?", params[:category_id]]) if !params[:category_id].blank?
|
||||
scope = scope.scoped(:conditions => ["#{DealProcess.table_name}.value = ?", status.id])
|
||||
scope = scope.scoped(:conditions => ["#{DealStatus.table_name}.is_closed = ?", params[:is_closed]]) if !params[:is_closed].blank?
|
||||
scope = scope.scoped(:conditions => ["#{DealProcess.table_name}.author_id = ?", params[:author_id]]) if !params[:author_id].blank?
|
||||
scope = scope.scoped(:conditions => ["#{DealProcess.table_name}.created_at BETWEEN ? AND ?", @from, @to]) if (@from && @to)
|
||||
# cond = ARCondition.new
|
||||
# cond << ["#{Deal.table_name}.project_id = ?", @project.id] if @project
|
||||
# cond << ["#{Deal.table_name}.category_id = ?", params[:category_id]] if !params[:category_id].blank?
|
||||
# cond << ["#{DealProcess.table_name}.value = ?", status.id]
|
||||
# cond << ["#{DealStatus.table_name}.is_closed = ?", params[:is_closed]] if !params[:is_closed].blank?
|
||||
# cond << ["#{DealProcess.table_name}.author_id = ?", params[:author_id]] if !params[:author_id].blank?
|
||||
#
|
||||
# retrieve_date_range
|
||||
# cond << ["#{DealProcess.table_name}.created_at BETWEEN ? AND ?", @from, @to] if (@from && @to)
|
||||
|
||||
@sale_funel << [status,
|
||||
scope.count(:select => "DISTINCT deal_id", :include => {:deal => :status}),
|
||||
scope.sum(:price,
|
||||
:select => "DISTINCT #{Deal.table_name}.price",
|
||||
:include => {:deal => :status},
|
||||
:group => :currency)
|
||||
]
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html{ render( :partial => "sale_funel", :layout => false) if request.xhr? }
|
||||
format.xml { render :xml => @sale_funel}
|
||||
format.json { render :text => @sale_funel.to_json, :layout => false }
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
124
app/controllers/tasks_controller.rb
Normal file
124
app/controllers/tasks_controller.rb
Normal file
@ -0,0 +1,124 @@
|
||||
class TasksController < ApplicationController
|
||||
unloadable
|
||||
|
||||
before_filter :find_project_by_project_id, :authorize, :except => [:index]
|
||||
before_filter :find_optional_project, :only => :index
|
||||
before_filter :find_taskable, :except => [:index, :add, :close]
|
||||
before_filter :find_issue, :except => [:index, :new]
|
||||
|
||||
def index
|
||||
cond = "(1=1)"
|
||||
# cond = "issues.assigned_to_id = #{User.current.id}"
|
||||
cond << " and issues.project_id = #{@project.id}" if @project
|
||||
cond << " and (issues.assigned_to_id = #{params[:assigned_to]})" unless params[:assigned_to].blank?
|
||||
|
||||
@tasks = Issue.visible.find(:all,
|
||||
:joins => "INNER JOIN tasks ON issues.id = tasks.issue_id",
|
||||
# :group => :issue_id,
|
||||
:conditions => cond,
|
||||
:order => "issues.due_date")
|
||||
@users = assigned_to_users
|
||||
end
|
||||
|
||||
def new
|
||||
issue = Issue.new
|
||||
issue.subject = params[:task_subject]
|
||||
issue.project = @project
|
||||
issue.tracker_id = params[:task_tracker]
|
||||
issue.author = User.current
|
||||
issue.due_date = params[:due_date]
|
||||
issue.assigned_to_id = params[:assigned_to]
|
||||
issue.description = params[:task_description]
|
||||
issue.status = IssueStatus.default
|
||||
if issue.save
|
||||
flash[:notice] = l(:notice_successful_add)
|
||||
@taskable.issues << issue
|
||||
@taskable.save
|
||||
redirect_to :back
|
||||
return
|
||||
else
|
||||
redirect_to :back
|
||||
end
|
||||
end
|
||||
|
||||
def add
|
||||
@show_form = "true"
|
||||
|
||||
if params[:source_id] && params[:source_type] && request.post? then
|
||||
find_taskable
|
||||
@taskable.issues << @issue
|
||||
@taskable.save
|
||||
end
|
||||
|
||||
taskable_name = @taskable.class.name.underscore
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html "issue_#{taskable_name}s", :partial => "issues/#{taskable_name}s"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
@issue.taskables.delete(@taskable)
|
||||
taskable_name = @taskable.class.name.underscore
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :back }
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page.replace_html "issue_#{taskable_name}s", :partial => "issues/#{taskable_name}s"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def close
|
||||
@issue.status = IssueStatus.find(:first, :conditions => { :is_closed => true })
|
||||
@issue.save
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
render :update do |page|
|
||||
page["issue_#{params[:issue_id]}"].visual_effect :fade
|
||||
end
|
||||
end
|
||||
format.html {redirect_to :back }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_taskable
|
||||
klass = Object.const_get(params[:source_type].camelcase)
|
||||
@taskable = klass.find(params[:source_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def find_issue
|
||||
@issue = Issue.find(params[:issue_id])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
end
|
||||
|
||||
def assigned_to_users
|
||||
user_values = []
|
||||
project = @project
|
||||
user_values << ["<< #{l(:label_all)} >>", ""]
|
||||
user_values << ["<< #{l(:label_me)} >>", User.current.id] if User.current.logged?
|
||||
if project
|
||||
user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
|
||||
else
|
||||
project_ids = Project.all(:conditions => Project.visible_condition(User.current)).collect(&:id)
|
||||
if project_ids.any?
|
||||
# members of the user's projects
|
||||
user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", project_ids]).sort.collect{|s| [s.name, s.id.to_s] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
355
app/helpers/contacts_helper.rb
Normal file
355
app/helpers/contacts_helper.rb
Normal file
@ -0,0 +1,355 @@
|
||||
module ContactsHelper
|
||||
|
||||
def authorized_for_permission?(permission, project, global = false)
|
||||
User.current.allowed_to?(permission, project, :global => global)
|
||||
end
|
||||
|
||||
def contacts_filters_for_select(selected)
|
||||
selected ||= ""
|
||||
options_for_select([[contacts_filter_name('0'), ActiveRecord::Base.connection.quoted_false.gsub(/'/, '')],
|
||||
[contacts_filter_name('1'), ActiveRecord::Base.connection.quoted_true.gsub(/'/, '')],
|
||||
[contacts_filter_name(''), ''],
|
||||
[contacts_filter_name('2'), '2'],
|
||||
[contacts_filter_name('3'), '3'],
|
||||
['--------'],
|
||||
[l(:button_cancel), '-1']], :selected => selected, :disabled => "--------")
|
||||
end
|
||||
|
||||
def contacts_filter_name(value)
|
||||
case value
|
||||
when '0'
|
||||
l(:label_all_people)
|
||||
when '1'
|
||||
l(:label_all_companies)
|
||||
when '2'
|
||||
l(:label_recently_added_contacts)
|
||||
when '3'
|
||||
l(:label_created_by_me)
|
||||
else
|
||||
l(:label_all_people_and_companies)
|
||||
end
|
||||
end
|
||||
|
||||
def skype_to(skype_name, name = nil)
|
||||
return link_to skype_name, 'skype:' + skype_name + '?call' unless skype_name.blank?
|
||||
end
|
||||
|
||||
def link_to_remote_list_update(text, url_params)
|
||||
link_to_remote(text,
|
||||
{:url => url_params, :method => :get, :update => 'contact_list', :complete => 'window.scrollTo(0,0)'},
|
||||
{:href => url_for(:params => url_params)}
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
def contacts_paginator(paginator, page_options)
|
||||
page_param = page_options.delete(:page_param) || :page
|
||||
per_page_links = page_options.delete(:per_page_links)
|
||||
url_param = params.dup
|
||||
# don't reuse query params if filters are present
|
||||
url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
|
||||
|
||||
html = ''
|
||||
if paginator.current.previous
|
||||
html << link_to_remote_list_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
|
||||
end
|
||||
|
||||
html << (pagination_links_each(paginator, page_options) do |n|
|
||||
link_to_remote_list_update(n.to_s, url_param.merge(page_param => n))
|
||||
end || '')
|
||||
|
||||
if paginator.current.next
|
||||
html << ' ' + link_to_remote_list_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next))
|
||||
end
|
||||
|
||||
html
|
||||
|
||||
end
|
||||
|
||||
def contact_url(contact)
|
||||
return {:controller => 'contacts', :action => 'show', :project_id => @project, :id => contact.id }
|
||||
end
|
||||
|
||||
def deal_url(deal)
|
||||
return {:controller => 'deals', :action => 'show', :id => deal.id }
|
||||
end
|
||||
|
||||
def note_source_url(note_source)
|
||||
polymorphic_url(note_source)
|
||||
# return {:controller => note_source.class.name.pluralize.downcase, :action => 'show', :project_id => @project, :id => note_source.id }
|
||||
end
|
||||
|
||||
def link_to_source(note_source, options={})
|
||||
return link_to note_source.name, note_source_url(note_source), options
|
||||
end
|
||||
|
||||
def avatar_to(obj, options = { })
|
||||
options[:size] = "64" unless options[:size]
|
||||
size = options[:size]
|
||||
options[:size] = options[:size] + "x" + options[:size]
|
||||
options[:class] = "gravatar"
|
||||
|
||||
avatar = obj.avatar unless Rails::env == "development"
|
||||
|
||||
if avatar && FileTest.exists?(avatar.diskfile) && avatar.is_thumbnailable? then # and obj.visible?
|
||||
avatar_url = url_for :only_path => false, :controller => 'attachments', :action => 'download', :id => avatar, :filename => avatar.filename
|
||||
thumbnail_url = url_for(:only_path => false,
|
||||
:controller => 'attachments',
|
||||
:action => 'thumbnail',
|
||||
:id => avatar,
|
||||
:size => size)
|
||||
|
||||
image_url = Object.const_defined?(:Magick) ? thumbnail_url : avatar_url
|
||||
|
||||
if options[:full_size] then
|
||||
image = link_to image_tag(image_url, options), avatar_url
|
||||
else
|
||||
image = image_tag(image_url, options)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
plugins_images = case obj
|
||||
when Deal then "deal.png"
|
||||
when Contact then obj.is_company ? "company.png" : "person.png"
|
||||
end
|
||||
plugins_images = image_path(plugins_images, :plugin => :redmine_contacts)
|
||||
|
||||
if !image && Setting.plugin_contacts[:use_gravatar] && obj.class == Contact
|
||||
options[:default] = "#{request.protocol}#{request.host_with_port}" + plugins_images
|
||||
options.merge!({:ssl => (defined?(request) && request.ssl?)})
|
||||
image = gravatar(obj.emails.first.downcase, options) rescue nil
|
||||
end
|
||||
|
||||
image ||= image_tag(plugins_images, options)
|
||||
|
||||
end
|
||||
|
||||
def link_to_add_phone(name)
|
||||
fields = '<p>' + label_tag(l(:field_contact_phone)) +
|
||||
text_field_tag( "contact[phones][]", '', :size => 30 ) +
|
||||
link_to_function(l(:label_remove), "removeField(this)") + '</p>'
|
||||
link_to_function(name, h("addField(this, '#{escape_javascript(fields)}' )"))
|
||||
end
|
||||
|
||||
def link_to_task_complete(url, bucket)
|
||||
onclick = "this.disable();"
|
||||
onclick << %Q/$("#{dom_id(pending, :name)}").style.textDecoration="line-through";/
|
||||
onclick << remote_function(:url => url, :method => :put, :with => "{ bucket: '#{bucket}' }")
|
||||
end
|
||||
|
||||
def render_contact_projects_hierarchy(projects)
|
||||
s = ''
|
||||
project_tree(projects) do |project, level|
|
||||
s << "<ul>"
|
||||
name_prefix = (level > 0 ? (' ' * 2 * level + '» ') : '')
|
||||
url = {:controller => 'contacts_projects',
|
||||
:action => 'delete',
|
||||
:disconnect_project_id => project.id,
|
||||
:project_id => @project.id,
|
||||
:contact_id => @contact.id}
|
||||
|
||||
s << "<li>" + name_prefix + link_to_project(project)
|
||||
s += ' ' + link_to_remote(image_tag('delete.png'),
|
||||
{:url => url},
|
||||
:href => url_for(url),
|
||||
:style => "vertical-align: middle",
|
||||
:class => "delete") if (projects.size > 1 && authorize_for(:contacts, :edit) )
|
||||
s << "</li>"
|
||||
|
||||
s << "</ul>"
|
||||
end
|
||||
s
|
||||
end
|
||||
|
||||
def contact_to_vcard(contact)
|
||||
return false if !Gem.available?('vpim')
|
||||
|
||||
require 'vpim/vcard'
|
||||
|
||||
card = Vpim::Vcard::Maker.make2 do |maker|
|
||||
|
||||
maker.add_name do |name|
|
||||
name.prefix = ''
|
||||
name.given = contact.first_name
|
||||
name.family = contact.last_name
|
||||
name.additional = contact.middle_name
|
||||
end
|
||||
|
||||
maker.add_addr do |addr|
|
||||
addr.preferred = true
|
||||
addr.street = contact.address.gsub("\r\n"," ").gsub("\n"," ")
|
||||
end
|
||||
|
||||
maker.title = contact.job_title
|
||||
maker.org = contact.company
|
||||
maker.birthday = contact.birthday.to_date unless contact.birthday.blank?
|
||||
maker.add_note(contact.background.gsub("\r\n"," ").gsub("\n", ' '))
|
||||
|
||||
maker.add_url(contact.website)
|
||||
|
||||
contact.phones.each { |phone| maker.add_tel(phone) }
|
||||
contact.emails.each { |email| maker.add_email(email) }
|
||||
end
|
||||
avatar = contact.attachments.find_by_description('avatar')
|
||||
card = card.encode.sub("END:VCARD", "PHOTO;BASE64:" + "\n " + [File.open(avatar.diskfile).read].pack('m').to_s.gsub(/[ \n]/, '').scan(/.{1,76}/).join("\n ") + "\nEND:VCARD") if avatar && avatar.readable?
|
||||
|
||||
card.to_s
|
||||
|
||||
end
|
||||
|
||||
def observe_fields(fields, options)
|
||||
#prepare a value of the :with parameter
|
||||
with = ""
|
||||
for field in fields
|
||||
with += "'"
|
||||
with += "&" if field != fields.first
|
||||
with += field + "='+escape($('#{field}').value)"
|
||||
with += " + " if field != fields.last
|
||||
end
|
||||
|
||||
#generate a call of the observer_field helper for each field
|
||||
ret = "";
|
||||
for field in fields
|
||||
ret += observe_field(field,
|
||||
options.merge( { :with => with }))
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
def contacts_to_vcard(contacts)
|
||||
contacts.map{|c| contact_to_vcard(c) }.join("\r\n")
|
||||
end
|
||||
|
||||
def contacts_to_csv(contacts)
|
||||
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
|
||||
decimal_separator = l(:general_csv_decimal_separator)
|
||||
export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
|
||||
# csv header fields
|
||||
headers = [ "#",
|
||||
l(:field_contact_first_name),
|
||||
l(:field_contact_middle_name),
|
||||
l(:field_contact_last_name),
|
||||
l(:field_contact_job_title),
|
||||
l(:field_contact_company),
|
||||
l(:field_contact_phone),
|
||||
l(:field_contact_email),
|
||||
l(:field_contact_address),
|
||||
l(:field_contact_skype),
|
||||
l(:field_contact_website),
|
||||
l(:field_birthday),
|
||||
l(:field_contact_tag_names),
|
||||
l(:field_contact_background)
|
||||
]
|
||||
# Export project custom fields if project is given
|
||||
# otherwise export custom fields marked as "For all projects"
|
||||
custom_fields = ContactCustomField.for_all
|
||||
custom_fields.each {|f| headers << f.name}
|
||||
# Description in the last column
|
||||
csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
||||
# csv lines
|
||||
contacts.each do |contact|
|
||||
fields = [contact.id,
|
||||
contact.first_name,
|
||||
contact.middle_name,
|
||||
contact.last_name,
|
||||
contact.job_title,
|
||||
contact.company,
|
||||
contact.phone,
|
||||
contact.email,
|
||||
contact.address.gsub("\r\n"," ").gsub("\n", ' '),
|
||||
contact.skype_name,
|
||||
contact.website,
|
||||
format_date(contact.birthday),
|
||||
contact.tag_list,
|
||||
contact.background.gsub("\r\n"," ").gsub("\n", ' ')
|
||||
]
|
||||
custom_fields.each {|f| fields << show_value(contact.custom_value_for(f)) }
|
||||
csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
|
||||
end
|
||||
end
|
||||
export
|
||||
end
|
||||
|
||||
# Renders a HTML/CSS tooltip
|
||||
#
|
||||
# To use, a trigger div is needed. This is a div with the class of "tooltip"
|
||||
# that contains this method wrapped in a span with the class of "tip"
|
||||
#
|
||||
# <div class="tooltip"><%= link_to_issue(issue) %>
|
||||
# <span class="tip"><%= render_issue_tooltip(issue) %></span>
|
||||
# </div>
|
||||
#
|
||||
def render_contact_tooltip(contact, options={})
|
||||
@cached_label_company ||= l(:field_contact_company)
|
||||
@cached_label_job_title = contact.is_company ? l(:field_company_field) : l(:field_contact_job_title)
|
||||
@cached_label_phone ||= l(:field_contact_phone)
|
||||
@cached_label_email ||= l(:field_contact_email)
|
||||
|
||||
emails = contact.emails.any? ? contact.emails.map{|email| "<span class=\"email\" style=\"white-space: nowrap;\">#{mail_to email}</span>"}.join(', ') : ''
|
||||
phones = contact.phones.any? ? contact.phones.map{|phone| "<span class=\"phone\" style=\"white-space: nowrap;\">#{phone}</span>"}.join(', ') : ''
|
||||
|
||||
s = link_to_contact(contact, options) + "<br /><br />"
|
||||
s << "<strong>#{@cached_label_job_title}</strong>: #{contact.job_title}<br />" unless contact.job_title.blank?
|
||||
s << "<strong>#{@cached_label_company}</strong>: #{link_to(contact.contact_company.name, {:controller => 'contacts', :action => 'show', :id => contact.contact_company.id })}<br />" if !contact.contact_company.blank? && !contact.is_company
|
||||
s << "<strong>#{@cached_label_email}</strong>: #{emails}<br />" if contact.emails.any?
|
||||
s << "<strong>#{@cached_label_phone}</strong>: #{phones}<br />" if contact.phones.any?
|
||||
s
|
||||
end
|
||||
|
||||
|
||||
def link_to_contact(contact, options={})
|
||||
s = ''
|
||||
html_options = {}
|
||||
html_options = {:class => 'icon icon-vcard'} if options[:icon] == true
|
||||
s << avatar_to(contact, :size => "16") if options[:avatar] == true
|
||||
s << link_to_source(contact, html_options)
|
||||
|
||||
s << "(#{contact.job_title}) " if (options[:job_title] == true) && !contact.job_title.blank?
|
||||
s << " #{l(:label_at_company)} " if (options[:job_title] == true) && !(contact.job_title.blank? or contact.company.blank?)
|
||||
if (options[:company] == true) and contact.contact_company
|
||||
s << link_to(contact.contact_company.name, {:controller => 'contacts', :action => 'show', :id => contact.contact_company.id })
|
||||
else
|
||||
h contact.company
|
||||
end
|
||||
s << "(#{l(:field_contact_tag_names)}: #{contact.tag_list.join(', ')}) " if (options[:tag_list] == true) && !contact.tag_list.blank?
|
||||
s
|
||||
end
|
||||
|
||||
def retrieve_query
|
||||
# session[:]
|
||||
# if !params[:query_id].blank?
|
||||
# cond = "project_id IS NULL"
|
||||
# cond << " OR project_id = #{@project.id}" if @project
|
||||
# @query = Query.find(params[:query_id], :conditions => cond)
|
||||
# raise ::Unauthorized unless @query.visible?
|
||||
# @query.project = @project
|
||||
# session[:query] = {:id => @query.id, :project_id => @query.project_id}
|
||||
# sort_clear
|
||||
# else
|
||||
# if api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
|
||||
# # Give it a name, required to be valid
|
||||
# @query = Query.new(:name => "_")
|
||||
# @query.project = @project
|
||||
# if params[:fields] || params[:f]
|
||||
# @query.filters = {}
|
||||
# @query.add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
|
||||
# else
|
||||
# @query.available_filters.keys.each do |field|
|
||||
# @query.add_short_filter(field, params[field]) if params[field]
|
||||
# end
|
||||
# end
|
||||
# @query.group_by = params[:group_by]
|
||||
# @query.column_names = params[:c] || (params[:query] && params[:query][:column_names])
|
||||
# session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names}
|
||||
# else
|
||||
# @query = Query.find_by_id(session[:query][:id]) if session[:query][:id]
|
||||
# @query ||= Query.new(:name => "_", :project => @project, :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names])
|
||||
# @query.project = @project
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
2
app/helpers/contacts_settings_helper.rb
Normal file
2
app/helpers/contacts_settings_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module ContactsSettingsHelper
|
||||
end
|
||||
29
app/helpers/contacts_tags_helper.rb
Normal file
29
app/helpers/contacts_tags_helper.rb
Normal file
@ -0,0 +1,29 @@
|
||||
module ContactsTagsHelper
|
||||
|
||||
def color_picker(name, color="#aaa")
|
||||
#build the hexes
|
||||
hexes = []
|
||||
(0..15).step(3) do |one|
|
||||
(0..15).step(3) do |two|
|
||||
(0..15).step(3) do |three|
|
||||
hexes << "#" + one.to_s(16) + two.to_s(16) + three.to_s(16)
|
||||
end
|
||||
end
|
||||
end
|
||||
arr = []
|
||||
on_change_function = "onChange=\"this.style.backgroundColor=this.options[this.selectedIndex].style.backgroundColor;\""
|
||||
10.times { arr << " " }
|
||||
returning html = '' do
|
||||
html << "<select name=#{name}[color] id=#{name}_color #{on_change_function} style=\"background-color:#{color};\">"
|
||||
html << hexes.collect {|c| "<option value='#{c.from(1).to_i(16)}'
|
||||
style='background-color: #{c}
|
||||
#{'selected="select"' if c == color }'>
|
||||
|
||||
#{c}
|
||||
|
||||
</option>" }.join("\n")
|
||||
html << "</select>"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
2
app/helpers/deal_categories_helper.rb
Normal file
2
app/helpers/deal_categories_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module DealCategoriesHelper
|
||||
end
|
||||
2
app/helpers/deal_statuses_helper.rb
Normal file
2
app/helpers/deal_statuses_helper.rb
Normal file
@ -0,0 +1,2 @@
|
||||
module DealStatusesHelper
|
||||
end
|
||||
104
app/helpers/deals_helper.rb
Normal file
104
app/helpers/deals_helper.rb
Normal file
@ -0,0 +1,104 @@
|
||||
module DealsHelper
|
||||
def collection_for_status_select
|
||||
deal_statuses.collect{|s| [s.name, s.id.to_s]}
|
||||
end
|
||||
|
||||
def collection_for_currencies_select
|
||||
[:en, :de, :'en-GB', :ru].each_with_index.collect{|l, index| [I18n.translate(:'number.currency.format.unit', :locale => l), index]}
|
||||
end
|
||||
|
||||
def deal_status_options_for_select(select="")
|
||||
options_for_select(collection_for_status_select, select)
|
||||
end
|
||||
|
||||
def deals_sum_to_currency(deals_sum)
|
||||
deals_sum.map{|c| content_tag(:span, deal_price_to_currency(c[1], c[0].to_i), :style => "white-space: nowrap;")}.join(' / ')
|
||||
end
|
||||
|
||||
def deal_price_to_currency(price, currency)
|
||||
case currency
|
||||
when 0
|
||||
locale = :en
|
||||
when 1
|
||||
locale = :de
|
||||
when 2
|
||||
locale = :'en-GB'
|
||||
when 3
|
||||
locale = :ru
|
||||
else
|
||||
end
|
||||
|
||||
!locale.blank? ? number_to_currency(price, :locale => locale.to_sym, :precision => 2) : number_with_delimiter(price, :delimiter => ' ', :precision => 2)
|
||||
end
|
||||
|
||||
def deal_price(deal)
|
||||
deal_price_to_currency(deal.price, deal.currency)
|
||||
end
|
||||
|
||||
def deal_statuses
|
||||
(!@project.blank? ? @project.deal_statuses : DealStatus.all(:order => "position")) || []
|
||||
end
|
||||
|
||||
def remove_contractor_link(contact)
|
||||
link_to_remote(image_tag('delete.png'),
|
||||
:url => {:controller => "deal_contacts", :action => 'delete', :project_id => @project, :deal_id => @deal, :contact_id => contact},
|
||||
:method => :delete,
|
||||
:confirm => l(:text_are_you_sure),
|
||||
:html => {:class => "delete", :title => l(:button_delete) }) if User.current.allowed_to?(:edit_deals, @project)
|
||||
end
|
||||
|
||||
def retrieve_date_range(period)
|
||||
@from, @to = nil, nil
|
||||
case period
|
||||
when 'today'
|
||||
@from = @to = Date.today
|
||||
when 'yesterday'
|
||||
@from = @to = Date.today - 1
|
||||
when 'current_week'
|
||||
@from = Date.today - (Date.today.cwday - 1)%7
|
||||
@to = @from + 6
|
||||
when 'last_week'
|
||||
@from = Date.today - 7 - (Date.today.cwday - 1)%7
|
||||
@to = @from + 6
|
||||
when '7_days'
|
||||
@from = Date.today - 7
|
||||
@to = Date.today
|
||||
when 'current_month'
|
||||
@from = Date.civil(Date.today.year, Date.today.month, 1)
|
||||
@to = (@from >> 1) - 1
|
||||
when 'last_month'
|
||||
@from = Date.civil(Date.today.year, Date.today.month, 1) << 1
|
||||
@to = (@from >> 1) - 1
|
||||
when '30_days'
|
||||
@from = Date.today - 30
|
||||
@to = Date.today
|
||||
when 'current_year'
|
||||
@from = Date.civil(Date.today.year, 1, 1)
|
||||
@to = Date.civil(Date.today.year, 12, 31)
|
||||
end
|
||||
|
||||
@from, @to = @from, @to + 1 if (@from && @to)
|
||||
|
||||
end
|
||||
|
||||
def retrieve_deals_query
|
||||
# debugger
|
||||
# params.merge!(session[:deals_query])
|
||||
# session[:deals_query] = {:project_id => @project.id, :status_id => params[:status_id], :category_id => params[:category_id], :assigned_to_id => params[:assigned_to_id]}
|
||||
if params[:status_id] || !params[:period].blank? || !params[:category_id].blank? || !params[:assigned_to_id].blank?
|
||||
session[:deals_query] = {:project_id => (@project ? @project.id : nil),
|
||||
:status_id => params[:status_id],
|
||||
:category_id => params[:category_id],
|
||||
:period => params[:period],
|
||||
:assigned_to_id => params[:assigned_to_id]}
|
||||
else
|
||||
if api_request? || params[:set_filter] || session[:deals_query].nil? || session[:deals_query][:project_id] != (@project ? @project.id : nil)
|
||||
session[:deals_query] = {}
|
||||
else
|
||||
params.merge!(session[:deals_query])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
95
app/helpers/notes_helper.rb
Normal file
95
app/helpers/notes_helper.rb
Normal file
@ -0,0 +1,95 @@
|
||||
module NotesHelper
|
||||
|
||||
def collection_for_note_types_select
|
||||
[:label_note_type_email, :label_note_type_call, :label_note_type_meeting].each_with_index.collect{|type, i| [l(type), i]}
|
||||
end
|
||||
|
||||
def note_type_icon(note)
|
||||
case note.type_id
|
||||
when 0
|
||||
content_tag('span', '', :class => "icon icon-email", :title => l(:label_note_type_email))
|
||||
when 1
|
||||
content_tag('span', '', :class => "icon icon-call", :title => l(:label_note_type_call))
|
||||
when 2
|
||||
content_tag('span', '', :class => "icon icon-meeting", :title => l(:label_note_type_meeting))
|
||||
else
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def add_note_ajax(note, note_source, show_info = false)
|
||||
render :update do |page|
|
||||
page[:add_note_form].reset
|
||||
page.insert_html :top, "notes", :partial => 'notes/note_item', :object => note, :locals => {:show_info => show_info, :note_source => note_source}
|
||||
page["note_#{@note.id}"].visual_effect :highlight
|
||||
end
|
||||
end
|
||||
|
||||
def render_contacts_notes(note, project, options={})
|
||||
content = ''
|
||||
editable = User.current.logged? && (User.current.allowed_to?(:edit_contact_notes, project) || (note.author == User.current && User.current.allowed_to?(:edit_own_contact_notes, project)))
|
||||
links = []
|
||||
if !note.description.blank?
|
||||
links << link_to_in_place_notes_editor(image_tag('edit.png'), "note-#{note.id}",
|
||||
{ :controller => 'notes', :action => 'edit', :id => note },
|
||||
:title => l(:button_edit)) if editable
|
||||
end
|
||||
content << content_tag('div', links.join(' '), :class => 'contextual') unless links.empty?
|
||||
content << textilizable(note, :description)
|
||||
css_classes = "wiki"
|
||||
css_classes << " editable" if editable
|
||||
content_tag('div', content, :id => "note-#{note.id}", :class => css_classes)
|
||||
end
|
||||
|
||||
def link_to_in_place_notes_editor(text, field_id, url, options={})
|
||||
onclick = "new Ajax.Request('#{url_for(url)}', {asynchronous:true, evalScripts:true, method:'get'}); return false;"
|
||||
link_to text, '#', options.merge(:onclick => onclick)
|
||||
end
|
||||
|
||||
def add_note_url(note_source, project=nil)
|
||||
{:controller => 'notes', :action => 'add_note', :source_id => note_source, :source_type => note_source.class.name, :project_id => project}
|
||||
end
|
||||
|
||||
def thumbnails(obj, options={})
|
||||
return false if !obj || !obj.respond_to?(:attachments)
|
||||
|
||||
options[:size] = options[:size].to_s || "100"
|
||||
size = options[:size]
|
||||
options[:size] = options[:size] + "x" + options[:size]
|
||||
# options[:max_width] = size
|
||||
# options[:max_heght] = size
|
||||
max_file_size = options[:max_file_size] || 300.kilobytes
|
||||
options[:class] = "thumbnail"
|
||||
|
||||
s = ""
|
||||
obj.attachments.each do |att_file|
|
||||
attachment_url = url_for :only_path => false, :controller => 'attachments', :action => 'download', :id => att_file, :filename => att_file.filename
|
||||
thumbnail_url = url_for(:only_path => false,
|
||||
:controller => 'attachments',
|
||||
:action => 'thumbnail',
|
||||
:id => att_file,
|
||||
:size => size)
|
||||
image_url = Object.const_defined?(:Magick) ? thumbnail_url : attachment_url
|
||||
s << link_to(image_tag(image_url, options), attachment_url, {:title => att_file.filename}) if (att_file.is_thumbnailable? && (att_file.filesize < max_file_size || Object.const_defined?(:Magick)))
|
||||
end
|
||||
s
|
||||
end
|
||||
|
||||
def auto_thumbnails(obj)
|
||||
s = ""
|
||||
max_file_size = Setting.plugin_contacts[:max_thumbnail_file_size].to_i.kilobytes if !Setting.plugin_contacts[:max_thumbnail_file_size].blank?
|
||||
s << thumbnails(obj, {:size => 100, :max_file_size => max_file_size}) if Setting.plugin_contacts[:auto_thumbnails]
|
||||
content_tag(:p, s, :class => "thumbnails") if !s.blank?
|
||||
end
|
||||
|
||||
def note_content(note)
|
||||
s = ""
|
||||
if note.content.length > Note.cut_length
|
||||
s << textilizable(truncate(note.content, {:length => Note.cut_length, :omission => "... \"#{l(:label_note_read_more)}\":#{url_for(:controller => 'notes', :action => 'show', :project_id => @project, :note_id => note)}" }))
|
||||
else
|
||||
s << textilizable(note, :content)
|
||||
end
|
||||
s
|
||||
end
|
||||
|
||||
end
|
||||
230
app/models/contact.rb
Normal file
230
app/models/contact.rb
Normal file
@ -0,0 +1,230 @@
|
||||
class Contact < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
CONTACT_FORMATS = {
|
||||
:lastname_firstname_middlename => '#{last_name} #{first_name} #{middle_name}',
|
||||
:firstname_middlename_lastname => '#{first_name} #{middle_name} #{last_name}',
|
||||
:firstname_lastname => '#{first_name} #{last_name}',
|
||||
:lastname_coma_firstname => '#{last_name}, #{first_name}'
|
||||
}
|
||||
|
||||
has_many :notes, :as => :source, :class_name => 'ContactNote', :dependent => :delete_all, :order => "created_on DESC"
|
||||
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
|
||||
has_and_belongs_to_many :issues, :order => "#{Issue.table_name}.due_date", :uniq => true
|
||||
has_many :deals, :order => "#{Deal.table_name}.status_id", :uniq => true
|
||||
has_and_belongs_to_many :related_deals, :class_name => 'Deal', :order => "#{Deal.table_name}.status_id", :uniq => true
|
||||
has_and_belongs_to_many :projects, :uniq => true
|
||||
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
||||
has_one :avatar, :class_name => "Attachment", :as => :container, :conditions => "#{Attachment.table_name}.description = 'avatar'", :dependent => :destroy
|
||||
|
||||
attr_accessor :phones
|
||||
attr_accessor :emails
|
||||
|
||||
acts_as_customizable
|
||||
acts_as_viewable
|
||||
acts_as_taggable
|
||||
acts_as_watchable
|
||||
acts_as_attachable :view_permission => :view_contacts,
|
||||
:delete_permission => :edit_contacts
|
||||
|
||||
|
||||
acts_as_event :datetime => :created_on,
|
||||
:url => Proc.new {|o| {:controller => 'contacts', :action => 'show', :id => o}},
|
||||
:type => 'icon-user',
|
||||
:title => Proc.new {|o| o.name },
|
||||
:description => Proc.new {|o| [o.info, o.company, o.email, o.address, o.background].join(' ') }
|
||||
|
||||
acts_as_searchable :columns => ["#{table_name}.first_name",
|
||||
"#{table_name}.middle_name",
|
||||
"#{table_name}.last_name",
|
||||
"#{table_name}.company",
|
||||
"#{table_name}.address",
|
||||
"#{table_name}.background"],
|
||||
:project_key => "#{Project.table_name}.id",
|
||||
:include => [:projects],
|
||||
# sort by id so that limited eager loading doesn't break with postgresql
|
||||
:order_column => "#{table_name}.id"
|
||||
|
||||
|
||||
|
||||
named_scope :visible, lambda {|*args| { :include => :projects,
|
||||
:conditions => Contact.allowed_to_condition(args.first || User.current, :view_contacts) }}
|
||||
named_scope :deletable, lambda {|*args| { :include => :projects,
|
||||
:conditions => Project.allowed_to_condition(args.first || User.current, :delete_contacts) }}
|
||||
|
||||
named_scope :editable, lambda {|*args| { :include => :projects,
|
||||
:conditions => Project.allowed_to_condition(args.first || User.current, :edit_contacts) }}
|
||||
|
||||
named_scope :in_project, lambda {|*args| { :include => :projects, :conditions => ["#{Project.table_name}.id = ?", args.first]}}
|
||||
|
||||
named_scope :like_by, lambda {|field, search| {:conditions => ["#{Contact.table_name}.#{field} LIKE ?", search + "%"] }}
|
||||
|
||||
named_scope :live_search, lambda {|search| {:conditions => ["(#{Contact.table_name}.first_name LIKE ? or
|
||||
#{Contact.table_name}.last_name LIKE ? or
|
||||
#{Contact.table_name}.middle_name LIKE ? or
|
||||
#{Contact.table_name}.company LIKE ? or
|
||||
#{Contact.table_name}.job_title LIKE ?)",
|
||||
"%" + search + "%",
|
||||
"%" + search + "%",
|
||||
"%" + search + "%",
|
||||
"%" + search + "%",
|
||||
"%" + search + "%"] }}
|
||||
|
||||
|
||||
named_scope :order_by_name, :order => "#{Contact.table_name}.last_name, #{Contact.table_name}.first_name"
|
||||
named_scope :order_by_creation, :order => "#{Contact.table_name}.created_on DESC"
|
||||
|
||||
# name or company is mandatory
|
||||
validates_presence_of :first_name
|
||||
validates_uniqueness_of :first_name, :scope => [:last_name, :middle_name, :company]
|
||||
validates_associated :projects
|
||||
|
||||
|
||||
def self.allowed_to_condition(user, permission, options={})
|
||||
Project.allowed_to_condition(user, permission)
|
||||
end
|
||||
|
||||
def all_deals
|
||||
@all_deals ||= (self.deals + self.related_deals ).uniq.sort!{|x, y| x.status_id <=> y.status_id }
|
||||
end
|
||||
|
||||
def all_visible_deals(usr=User.current)
|
||||
@all_deals ||= (self.deals.visible(usr) + self.related_deals.visible(usr)).uniq.sort!{|x, y| x.status_id <=> y.status_id }
|
||||
end
|
||||
|
||||
def self.available_tags(options = {})
|
||||
name_like = options[:name_like]
|
||||
limit = options[:limit]
|
||||
options = {}
|
||||
|
||||
if name_like
|
||||
options[:conditions] = ["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", "%#{name_like.downcase}%"]
|
||||
end
|
||||
options[:limit] = limit if limit
|
||||
self.all_tag_counts(options)
|
||||
end
|
||||
|
||||
def duplicates(limit=5)
|
||||
scope = Contact.scoped({})
|
||||
scope = scope.like_by("first_name", self.first_name.strip) if !self.first_name.blank?
|
||||
scope = scope.like_by("middle_name", self.middle_name.strip) if !self.middle_name.blank?
|
||||
scope = scope.like_by("last_name", self.last_name.strip) if !self.last_name.blank?
|
||||
scope = scope.scoped(:conditions => ["#{Contact.table_name}.id <> ?", self.id]) if !self.new_record?
|
||||
@duplicates ||= (self.first_name.blank? && self.last_name.blank? && self.middle_name.blank?) ? [] : scope.visible.find(:all, :limit => limit)
|
||||
end
|
||||
|
||||
def employees
|
||||
@employees ||= Contact.order_by_name.find(:all, :conditions => ["#{Contact.table_name}.company = ? AND #{Contact.table_name}.id <> ?", self.first_name, self.id])
|
||||
end
|
||||
|
||||
def redmine_user
|
||||
@redmine_user ||= User.find(:first, :conditions => {:mail => emails}) unless self.email.blank?
|
||||
end
|
||||
|
||||
def contact_company
|
||||
@contact_company ||= Contact.find_by_first_name(self.company)
|
||||
end
|
||||
|
||||
def notes_attachments
|
||||
@contact_attachments ||= Attachment.find(:all,
|
||||
:conditions => { :container_type => "Note", :container_id => self.notes.map(&:id)},
|
||||
:order => "created_on DESC")
|
||||
end
|
||||
|
||||
# usr for mailer
|
||||
def visible?(usr=nil)
|
||||
usr ||= User.current
|
||||
@visible ||= 0 < self.projects.visible(usr).count(:conditions => Project.allowed_to_condition(usr, :view_contacts))
|
||||
end
|
||||
|
||||
def editable?(usr=nil)
|
||||
usr ||= User.current
|
||||
@editable ||= 0 < self.projects.visible(usr).count(:conditions => Project.allowed_to_condition(usr, :edit_contacts))
|
||||
end
|
||||
|
||||
def deletable?(usr=nil)
|
||||
usr ||= User.current
|
||||
@deletable ||= 0 < self.projects.visible(usr).count(:conditions => Project.allowed_to_condition(usr, :delete_contacts))
|
||||
end
|
||||
|
||||
def send_mail_allowed?(usr=nil)
|
||||
usr ||= User.current
|
||||
@send_mail_allowed ||= 0 < self.projects.visible(usr).count(:conditions => Project.allowed_to_condition(usr, :send_contacts_mail))
|
||||
end
|
||||
|
||||
def self.projects_joins
|
||||
joins = []
|
||||
joins << ["JOIN contacts_projects ON contacts_projects.contact_id = #{self.table_name}.id"]
|
||||
joins << ["JOIN #{Project.table_name} ON contacts_projects.project_id = #{Project.table_name}.id"]
|
||||
end
|
||||
|
||||
def project(current_project=nil)
|
||||
return @project if @project
|
||||
if current_project && self.projects.visible.include?(current_project)
|
||||
@project = current_project
|
||||
else
|
||||
@project = self.projects.visible.find(:first, :conditions => Project.allowed_to_condition(User.current, :view_contacts))
|
||||
end
|
||||
|
||||
@project ||= self.projects.first
|
||||
end
|
||||
|
||||
def self.find_by_emails(emails)
|
||||
contact = nil
|
||||
cond = "(1 = 0)"
|
||||
emails.each do |mail|
|
||||
cond << " OR (#{Contact.table_name}.email LIKE '%#{mail.downcase}%')"
|
||||
end
|
||||
Contact.find(:all, :conditions => cond)
|
||||
end
|
||||
|
||||
|
||||
def name(formatter=nil)
|
||||
if !self.is_company
|
||||
if formatter
|
||||
eval('"' + (CONTACT_FORMATS[formatter] || CONTACT_FORMATS[:firstname_lastname]) + '"')
|
||||
else
|
||||
@name ||= eval('"' + (Setting.plugin_contacts[:name_format] && CONTACT_FORMATS[Setting.plugin_contacts[:name_format].to_sym] || CONTACT_FORMATS[:firstname_lastname]) + '"')
|
||||
end
|
||||
|
||||
# [self.last_name, self.first_name, self.middle_name].each {|field| result << field unless field.blank?}
|
||||
else
|
||||
self.first_name
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def info
|
||||
self.job_title
|
||||
end
|
||||
|
||||
def phones
|
||||
@phones || self.phone ? self.phone.split( /, */) : []
|
||||
end
|
||||
|
||||
def emails
|
||||
@emails || self.email ? self.email.split( /, */).map{|m| m.strip} : []
|
||||
end
|
||||
|
||||
def age
|
||||
return nil if birthday.blank?
|
||||
now = Time.now
|
||||
# how many years?
|
||||
# has their birthday occured this year yet?
|
||||
# subtract 1 if so, 0 if not
|
||||
age = now.year - birthday.year - (birthday.to_time.change(:year => now.year) > now ? 1 : 0)
|
||||
end
|
||||
|
||||
def website_address
|
||||
self.website.match("^https?://") ? self.website : self.website.gsub(/^/, "http://") unless self.website.blank?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_phone
|
||||
if @phones
|
||||
self.phone = @phones.uniq.map {|s| s.strip.delete(',').squeeze(" ")}.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
7
app/models/contact_custom_field.rb
Normal file
7
app/models/contact_custom_field.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class ContactCustomField < CustomField
|
||||
unloadable
|
||||
|
||||
def type_name
|
||||
:label_contact_plural
|
||||
end
|
||||
end
|
||||
20
app/models/contact_note.rb
Normal file
20
app/models/contact_note.rb
Normal file
@ -0,0 +1,20 @@
|
||||
class ContactNote < Note
|
||||
unloadable
|
||||
belongs_to :contact, :foreign_key => :source_id
|
||||
|
||||
acts_as_searchable :columns => ["#{table_name}.content"],
|
||||
:include => [:contact => :projects],
|
||||
:project_key => "#{Project.table_name}.id",
|
||||
:permission => :view_contacts,
|
||||
# sort by id so that limited eager loading doesn't break with postgresql
|
||||
:order_column => "#{table_name}.id"
|
||||
|
||||
acts_as_activity_provider :type => 'contacts',
|
||||
:permission => :view_contacts,
|
||||
:author_key => :author_id,
|
||||
:find_options => {:include => [:contact => :projects], :conditions => {:source_type => 'Contact'} }
|
||||
|
||||
named_scope :visible, lambda {|*args| {:include => [:contact => :projects],
|
||||
:conditions => Contact.allowed_to_condition(args.first || User.current, :view_contacts)+
|
||||
" AND (#{DealNote.table_name}.source_type = 'Contact')"}}
|
||||
end
|
||||
311
app/models/contacts_mailer.rb
Normal file
311
app/models/contacts_mailer.rb
Normal file
@ -0,0 +1,311 @@
|
||||
class ContactsMailer < ActionMailer::Base
|
||||
include Redmine::I18n
|
||||
|
||||
class UnauthorizedAction < StandardError; end
|
||||
class MissingInformation < StandardError; end
|
||||
|
||||
helper :application
|
||||
|
||||
attr_reader :email, :user
|
||||
|
||||
def self.default_url_options
|
||||
h = Setting.host_name
|
||||
h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
|
||||
{ :host => h, :protocol => Setting.protocol }
|
||||
end
|
||||
|
||||
def bulk_mail(contact, params = {})
|
||||
raise l(:error_empty_email) if (contact.emails.empty? || params[:message].blank?)
|
||||
|
||||
from params[:from] || User.current.mail
|
||||
recipients contact.emails.first
|
||||
bcc params[:bcc]
|
||||
subject params[:subject]
|
||||
|
||||
body :contact => contact, :params => params
|
||||
|
||||
content_type "multipart/mixed"
|
||||
|
||||
part "multipart/alternative" do |alternative|
|
||||
alternative.part :content_type => "text/plain", :body => render(:file => "bulk_mail.text.plain.rhtml", :body => body)
|
||||
alternative.part :content_type => "text/html", :body => render_message("bulk_mail.text.html.rhtml", body)
|
||||
end
|
||||
|
||||
params[:attachments].each_value do |mail_attachment|
|
||||
unless mail_attachment['file'].blank?
|
||||
mail_attachment['file'].rewind
|
||||
attachment :content_type => mail_attachment['file'].content_type, :body => mail_attachment['file'].read, :filename => mail_attachment['file'].original_filename
|
||||
mail_attachment['file'].rewind
|
||||
end
|
||||
end unless params[:attachments].blank?
|
||||
|
||||
end
|
||||
|
||||
def self.receive(email, options={})
|
||||
@@contacts_mailer_options = options.dup
|
||||
super email
|
||||
end
|
||||
|
||||
|
||||
# Processes incoming emails
|
||||
# Returns the created object (eg. an issue, a message) or false
|
||||
def receive(email)
|
||||
@email = email
|
||||
sender_email = email.from.to_a.first.to_s.strip
|
||||
# Ignore emails received from the application emission address to avoid hell cycles
|
||||
if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
|
||||
logger.info "MailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
|
||||
return false
|
||||
end
|
||||
@user = User.find_by_mail(sender_email) if sender_email.present?
|
||||
if @user.nil? || (@user && !@user.active?)
|
||||
logger.info "MailHandler: ignoring email from unknown user [#{sender_email}]" if logger && logger.info
|
||||
end
|
||||
dispatch
|
||||
end
|
||||
|
||||
def dispatch
|
||||
|
||||
deal_id = email.to.to_s.match(/.+\+d([0-9]*)/).to_a[1]
|
||||
if deal_id
|
||||
deal = Deal.find_by_id(deal_id)
|
||||
if deal
|
||||
return [*receive_deal_note(deal_id)]
|
||||
end
|
||||
end
|
||||
|
||||
contacts = []
|
||||
|
||||
if contacts.blank?
|
||||
contact_id = email.to.to_s.match(/.+\+c([0-9]*)/).to_a[1]
|
||||
contacts = Contact.find_all_by_id(contact_id)
|
||||
end
|
||||
|
||||
if contacts.blank?
|
||||
contacts = Contact.find_by_emails(email.to.to_a)
|
||||
end
|
||||
|
||||
if contacts.blank?
|
||||
# debugger
|
||||
from_key_words = get_keyword_locales(:label_mail_from)
|
||||
@plain_text_body = plain_text_body.gsub(/^>\s*/, '')
|
||||
full_address = plain_text_body.match(/^(#{from_key_words.join('|')})[ \s]*:[ \s]*(.+)\s*$/).to_a[2]
|
||||
|
||||
email_address = full_address.match(/[\w,\.,\-,\+]+@.+\.\w{2,}/) if full_address
|
||||
contacts = Contact.find_by_emails(email_address.to_s.strip.to_a) if email_address
|
||||
end
|
||||
|
||||
if contacts.blank?
|
||||
return false
|
||||
end
|
||||
|
||||
raise MissingInformation if contacts.blank?
|
||||
|
||||
result = []
|
||||
contacts.each do |contact|
|
||||
result << receive_contact_note(contact.id)
|
||||
end
|
||||
result
|
||||
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
# TODO: send a email to the user
|
||||
logger.error e.message if logger
|
||||
false
|
||||
rescue MissingInformation => e
|
||||
logger.error "MailHandler: missing information from #{user}: #{e.message}" if logger
|
||||
false
|
||||
rescue UnauthorizedAction => e
|
||||
logger.error "MailHandler: unauthorized attempt from #{user}" if logger
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
# Receives a reply to a forum message
|
||||
def receive_contact_note(contact_id)
|
||||
contact = Contact.find_by_id(contact_id)
|
||||
note = nil
|
||||
# logger.error "MailHandler: receive_contact_note user: #{user},
|
||||
# contact: #{contact.name},
|
||||
# editable: #{contact.editable?(self.user)},
|
||||
# current: #{User.current}"
|
||||
raise UnauthorizedAction unless contact.editable?(self.user)
|
||||
if contact
|
||||
note = ContactNote.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
|
||||
:type_id => Note.note_types[:email],
|
||||
:content => plain_text_body,
|
||||
:created_on => email.date)
|
||||
note.author = self.user
|
||||
contact.notes << note
|
||||
add_attachments(note)
|
||||
logger.info note
|
||||
note.save
|
||||
contact.save
|
||||
end
|
||||
note
|
||||
end
|
||||
|
||||
def receive_deal_note(deal_id)
|
||||
deal = Deal.find_by_id(deal_id)
|
||||
note = nil
|
||||
# logger.error "MailHandler: receive_contact_note user: #{user},
|
||||
# contact: #{contact.name},
|
||||
# editable: #{contact.editable?(self.user)},
|
||||
# current: #{User.current}"
|
||||
raise UnauthorizedAction unless deal.editable?(self.user)
|
||||
if deal
|
||||
note = DealNote.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
|
||||
:type_id => Note.note_types[:email],
|
||||
:content => plain_text_body,
|
||||
:created_on => email.date)
|
||||
note.author = self.user
|
||||
deal.notes << note
|
||||
add_attachments(note)
|
||||
logger.info note
|
||||
note.save
|
||||
deal.save
|
||||
end
|
||||
note
|
||||
end
|
||||
|
||||
|
||||
def self.check_imap(imap_options={}, options={})
|
||||
require 'net/imap'
|
||||
|
||||
host = imap_options[:host] || '127.0.0.1'
|
||||
port = imap_options[:port] || '143'
|
||||
ssl = !imap_options[:ssl].nil?
|
||||
folder = imap_options[:folder] || 'INBOX'
|
||||
|
||||
imap = Net::IMAP.new(host, port, ssl)
|
||||
imap.login(imap_options[:username], imap_options[:password]) unless imap_options[:username].nil?
|
||||
imap.select(folder)
|
||||
imap.search(['NOT', 'SEEN']).each do |message_id|
|
||||
msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822']
|
||||
logger.debug "Receiving message #{message_id}" if logger && logger.debug?
|
||||
if ContactsMailer.receive(msg, options)
|
||||
logger.debug "Message #{message_id} successfully received" if logger && logger.debug?
|
||||
if imap_options[:move_on_success]
|
||||
imap.copy(message_id, imap_options[:move_on_success])
|
||||
end
|
||||
imap.store(message_id, "+FLAGS", [:Seen, :Deleted])
|
||||
else
|
||||
logger.debug "Message #{message_id} can not be processed" if logger && logger.debug?
|
||||
imap.store(message_id, "+FLAGS", [:Seen])
|
||||
if imap_options[:move_on_failure]
|
||||
imap.copy(message_id, imap_options[:move_on_failure])
|
||||
imap.store(message_id, "+FLAGS", [:Deleted])
|
||||
end
|
||||
end
|
||||
end
|
||||
imap.expunge
|
||||
end
|
||||
|
||||
def self.check_pop3(pop_options={}, options={})
|
||||
require 'net/pop'
|
||||
|
||||
host = pop_options[:host] || '127.0.0.1'
|
||||
port = pop_options[:port] || '110'
|
||||
apop = (pop_options[:apop].to_s == '1')
|
||||
delete_unprocessed = (pop_options[:delete_unprocessed].to_s == '1')
|
||||
|
||||
pop = Net::POP3.APOP(apop).new(host,port)
|
||||
logger.debug "Connecting to #{host}..." if logger && logger.debug?
|
||||
pop.start(pop_options[:username], pop_options[:password]) do |pop_session|
|
||||
if pop_session.mails.empty?
|
||||
logger.debug "No email to process" if logger && logger.debug?
|
||||
else
|
||||
logger.debug "#{pop_session.mails.size} email(s) to process..." if logger && logger.debug?
|
||||
pop_session.each_mail do |msg|
|
||||
message = msg.pop
|
||||
message_id = (message =~ /^Message-ID: (.*)/ ? $1 : '').strip
|
||||
if ContactsMailer.receive(message, options)
|
||||
msg.delete
|
||||
logger.debug "--> Message #{message_id} processed and deleted from the server" if logger && logger.debug?
|
||||
else
|
||||
if delete_unprocessed
|
||||
msg.delete
|
||||
logger.debug "--> Message #{message_id} NOT processed and deleted from the server" if logger && logger.debug?
|
||||
else
|
||||
logger.debug "--> Message #{message_id} NOT processed and left on the server" if logger && logger.debug?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Destructively extracts the value for +attr+ in +text+
|
||||
# Returns nil if no matching keyword found
|
||||
def extract_keyword!(text, attr, format=nil)
|
||||
keys = [attr.to_s.humanize]
|
||||
if attr.is_a?(Symbol)
|
||||
keys << l("field_#{attr}", :default => '', :locale => user.language) if user && user.language.present?
|
||||
keys << l("field_#{attr}", :default => '', :locale => Setting.default_language) if Setting.default_language.present?
|
||||
end
|
||||
keys.reject! {|k| k.blank?}
|
||||
keys.collect! {|k| Regexp.escape(k)}
|
||||
format ||= '.+'
|
||||
text.gsub!(/^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i, '') # /^(От:)[ \t]*:[ \t]*(.+)\s*$/i
|
||||
$2 && $2.strip
|
||||
end
|
||||
|
||||
|
||||
def add_attachments(obj)
|
||||
if email.has_attachments?
|
||||
email.attachments.each do |attachment|
|
||||
Attachment.create(:container => obj,
|
||||
:file => attachment,
|
||||
:author => user,
|
||||
:content_type => attachment.content_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the text/plain part of the email
|
||||
# If not found (eg. HTML-only email), returns the body with tags removed
|
||||
def plain_text_body
|
||||
return @plain_text_body unless @plain_text_body.nil?
|
||||
parts = @email.parts.collect {|c| (c.respond_to?(:parts) && !c.parts.empty?) ? c.parts : c}.flatten
|
||||
if parts.empty?
|
||||
parts << @email
|
||||
end
|
||||
plain_text_part = parts.detect {|p| p.content_type == 'text/plain'}
|
||||
if plain_text_part.nil?
|
||||
# no text/plain part found, assuming html-only email
|
||||
# strip html tags and remove doctype directive
|
||||
@plain_text_body = ActionController::Base.helpers.strip_tags(@email.body.to_s)
|
||||
@plain_text_body.gsub! %r{^<!DOCTYPE .*$}, ''
|
||||
else
|
||||
@plain_text_body = plain_text_part.body.to_s
|
||||
end
|
||||
@plain_text_body.strip!
|
||||
@plain_text_body
|
||||
end
|
||||
|
||||
def get_keyword_locales(keyword)
|
||||
I18n.available_locales.collect{|lc| l(keyword, :locale => lc)}.uniq
|
||||
end
|
||||
|
||||
# Appends a Redmine header field (name is prepended with 'X-Redmine-')
|
||||
def redmine_headers(h)
|
||||
h.each { |k,v| headers["X-Redmine-#{k}"] = v }
|
||||
end
|
||||
|
||||
def initialize_defaults(method_name)
|
||||
super
|
||||
# Common headers
|
||||
headers 'X-Mailer' => 'Redmine Contacts',
|
||||
'X-Redmine-Host' => Setting.host_name,
|
||||
'X-Redmine-Site' => Setting.app_title
|
||||
end
|
||||
|
||||
def logger
|
||||
RAILS_DEFAULT_LOGGER
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
71
app/models/contacts_setting.rb
Normal file
71
app/models/contacts_setting.rb
Normal file
@ -0,0 +1,71 @@
|
||||
class ContactsSetting < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
belongs_to :project
|
||||
|
||||
cattr_accessor :settings
|
||||
|
||||
# Hash used to cache setting values
|
||||
@cached_settings = {}
|
||||
@cached_cleared_on = Time.now
|
||||
|
||||
validates_uniqueness_of :name, :scope => [:project_id]
|
||||
|
||||
|
||||
def value
|
||||
v = read_attribute(:value)
|
||||
# Unserialize serialized settings
|
||||
v = YAML::load(v) if v.is_a?(String)
|
||||
v
|
||||
end
|
||||
|
||||
def value=(v)
|
||||
v = v.to_yaml if v
|
||||
write_attribute(:value, v.to_s)
|
||||
end
|
||||
|
||||
# Returns the value of the setting named name
|
||||
def self.[](name, project_id)
|
||||
v = @cached_settings[hk(name, project_id)]
|
||||
v ? v : (@cached_settings[hk(name, project_id)] = find_or_default(name, project_id).value)
|
||||
end
|
||||
|
||||
def self.[]=(name, project_id, v)
|
||||
setting = find_or_default(name, project_id)
|
||||
setting.value = (v ? v : "")
|
||||
@cached_settings[hk(name, project_id)] = nil
|
||||
setting.save
|
||||
@cached_settings.clear
|
||||
@cached_cleared_on = Time.now
|
||||
|
||||
#TODO: Create global contacts controller and add check_cache to it
|
||||
setting.value
|
||||
end
|
||||
|
||||
# Checks if settings have changed since the values were read
|
||||
# and clears the cache hash if it's the case
|
||||
# Called once per request
|
||||
def self.check_cache
|
||||
settings_updated_on = ContactsSetting.maximum(:updated_on)
|
||||
if settings_updated_on && @cached_cleared_on <= settings_updated_on
|
||||
@cached_settings.clear
|
||||
@cached_cleared_on = Time.now
|
||||
logger.info "Settings cache cleared." if logger
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.hk(name, project_id)
|
||||
"#{name}-#{project_id.to_s}"
|
||||
end
|
||||
|
||||
# Returns the Setting instance for the setting named name
|
||||
# (record found in database or new record with default value)
|
||||
def self.find_or_default(name, project_id)
|
||||
name = name.to_s
|
||||
setting = find_by_name_and_project_id(name, project_id)
|
||||
setting ||= new(:name => name, :value => '', :project_id => project_id)
|
||||
end
|
||||
|
||||
end
|
||||
15
app/models/contacts_tasks.rb
Normal file
15
app/models/contacts_tasks.rb
Normal file
@ -0,0 +1,15 @@
|
||||
class ContactsTasks < ActiveRecord::Base
|
||||
validates_presence_of :contact_id, :issue_id
|
||||
validates_uniqueness_of :contact_id, :scope => [:issue_id]
|
||||
|
||||
after_create :send_mails
|
||||
after_save :send_mails
|
||||
|
||||
private
|
||||
|
||||
def send_mails
|
||||
Mailer.deliver_contacts_issue_connected(Contact.find(self.contact_id), Issue.find(self.issue_id))
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
120
app/models/deal.rb
Normal file
120
app/models/deal.rb
Normal file
@ -0,0 +1,120 @@
|
||||
class Deal < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
|
||||
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
|
||||
belongs_to :category, :class_name => 'DealCategory', :foreign_key => 'category_id'
|
||||
belongs_to :contact
|
||||
belongs_to :status, :class_name => "DealStatus", :foreign_key => "status_id"
|
||||
has_many :deals, :class_name => "deal", :foreign_key => "reference_id"
|
||||
has_many :notes, :as => :source, :class_name => 'DealNote', :dependent => :delete_all, :order => "created_on DESC"
|
||||
has_many :deal_processes, :dependent => :delete_all
|
||||
has_and_belongs_to_many :related_contacts, :class_name => 'Contact', :order => "#{Contact.table_name}.last_name, #{Contact.table_name}.first_name", :uniq => true
|
||||
|
||||
|
||||
named_scope :visible, lambda {|*args| { :include => :project,
|
||||
:conditions => Project.allowed_to_condition(args.first || User.current, :view_deals)} }
|
||||
|
||||
named_scope :deletable, lambda {|*args| { :include => :project,
|
||||
:conditions => Project.allowed_to_condition(args.first || User.current, :delete_deals) }}
|
||||
|
||||
named_scope :live_search, lambda {|search| {:conditions => ["(#{Deal.table_name}.name LIKE ?)", "%#{search}%"] }}
|
||||
|
||||
named_scope :open, :include => :status, :conditions => ["#{DealStatus.table_name}.is_closed = ?", false]
|
||||
|
||||
acts_as_customizable
|
||||
acts_as_viewable
|
||||
acts_as_watchable
|
||||
acts_as_attachable :view_permission => :view_deals,
|
||||
:delete_permission => :edit_deals
|
||||
|
||||
acts_as_event :datetime => :created_on,
|
||||
:url => Proc.new {|o| {:controller => 'deals', :action => 'show', :id => o}},
|
||||
:type => 'icon-report',
|
||||
:title => Proc.new {|o| o.name },
|
||||
:description => Proc.new {|o| [o.price, o.contact ? o.contact.name : nil, o.background].join(' ').strip }
|
||||
|
||||
acts_as_searchable :columns => ["#{table_name}.name",
|
||||
"#{table_name}.background"],
|
||||
:include => [:project],
|
||||
# sort by id so that limited eager loading doesn't break with postgresql
|
||||
:order_column => "#{table_name}.id"
|
||||
|
||||
|
||||
|
||||
validates_presence_of :name
|
||||
validates_numericality_of :price, :allow_nil => true
|
||||
|
||||
after_save :create_deal_process
|
||||
|
||||
|
||||
include ActionView::Helpers::NumberHelper
|
||||
include ::DealsHelper
|
||||
|
||||
def after_initialize
|
||||
if new_record?
|
||||
# set default values for new records only
|
||||
self.status ||= DealStatus.default
|
||||
end
|
||||
end
|
||||
|
||||
def avatar
|
||||
|
||||
end
|
||||
|
||||
def full_name
|
||||
result = ''
|
||||
result << self.contact.name + ": " unless self.contact.blank?
|
||||
result << self.name
|
||||
end
|
||||
|
||||
def all_contacts
|
||||
@all_contacts ||= ([self.contact] + self.related_contacts ).uniq
|
||||
end
|
||||
|
||||
def self.available_users(prj=nil)
|
||||
cond = "(1=1)"
|
||||
cond << " AND #{Deal.table_name}.project_id = #{prj.id}" if prj
|
||||
User.active.find(:all, :select => "DISTINCT #{User.table_name}.*", :joins => "JOIN #{Deal.table_name} ON #{Deal.table_name}.assigned_to_id = #{User.table_name}.id", :conditions => cond, :order => "#{User.table_name}.lastname, #{User.table_name}.firstname")
|
||||
end
|
||||
|
||||
|
||||
def init_deal_process(author)
|
||||
@current_deal_process ||= DealProcess.new(:deal => self, :author => (author || User.current))
|
||||
@deal_status_before_change = self.new_record? ? nil : self.status_id
|
||||
updated_on_will_change!
|
||||
@current_deal_process
|
||||
end
|
||||
|
||||
def create_deal_process
|
||||
if @current_deal_process && !(@deal_status_before_change == self.status_id)
|
||||
@current_deal_process.old_value = @deal_status_before_change
|
||||
@current_deal_process.value = self.status_id
|
||||
@current_deal_process.save
|
||||
# reset current journal
|
||||
init_deal_process @current_deal_process.author
|
||||
end
|
||||
end
|
||||
|
||||
def visible?(usr=nil)
|
||||
(usr || User.current).allowed_to?(:view_deals, self.project)
|
||||
end
|
||||
|
||||
def editable?(usr=nil)
|
||||
(usr || User.current).allowed_to?(:edit_deals, self.project)
|
||||
end
|
||||
|
||||
def destroyable?(usr=nil)
|
||||
(usr || User.current).allowed_to?(:delete_deals, self.project)
|
||||
end
|
||||
|
||||
|
||||
def info
|
||||
result = ""
|
||||
result = self.status.name if self.status
|
||||
result = result + " - " + deal_price(self) if !self.price.blank?
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
26
app/models/deal_category.rb
Normal file
26
app/models/deal_category.rb
Normal file
@ -0,0 +1,26 @@
|
||||
class DealCategory < ActiveRecord::Base
|
||||
unloadable
|
||||
belongs_to :project
|
||||
has_many :deals, :foreign_key => 'category_id', :dependent => :nullify
|
||||
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name, :scope => [:project_id]
|
||||
validates_length_of :name, :maximum => 30
|
||||
|
||||
alias :destroy_without_reassign :destroy
|
||||
|
||||
# Destroy the category
|
||||
# If a category is specified, issues are reassigned to this category
|
||||
def destroy(reassign_to = nil)
|
||||
if reassign_to && reassign_to.is_a?(DealCategory) && reassign_to.project == self.project
|
||||
Deal.update_all("category_id = #{reassign_to.id}", "category_id = #{id}")
|
||||
end
|
||||
destroy_without_reassign
|
||||
end
|
||||
|
||||
def <=>(category)
|
||||
name <=> category.name
|
||||
end
|
||||
|
||||
def to_s; name end
|
||||
end
|
||||
7
app/models/deal_custom_field.rb
Normal file
7
app/models/deal_custom_field.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class DealCustomField < CustomField
|
||||
unloadable
|
||||
|
||||
def type_name
|
||||
:label_deal_plural
|
||||
end
|
||||
end
|
||||
23
app/models/deal_note.rb
Normal file
23
app/models/deal_note.rb
Normal file
@ -0,0 +1,23 @@
|
||||
class DealNote < Note
|
||||
unloadable
|
||||
belongs_to :deal, :foreign_key => :source_id
|
||||
|
||||
acts_as_searchable :columns => ["#{table_name}.content"],
|
||||
:include => [:deal => :project],
|
||||
:project_key => "#{Project.table_name}.id",
|
||||
:permission => :view_deals,
|
||||
# sort by id so that limited eager loading doesn't break with postgresql
|
||||
:order_column => "#{table_name}.id"
|
||||
|
||||
acts_as_activity_provider :type => 'contacts',
|
||||
:permission => :view_deals,
|
||||
:author_key => :author_id,
|
||||
:find_options => {:include => [:deal => :project],
|
||||
:conditions => {:source_type => 'Deal'}}
|
||||
|
||||
named_scope :visible, lambda {|*args| { :include => [:deal => :project],
|
||||
:conditions => Project.allowed_to_condition(args.first || User.current, :view_deals) +
|
||||
" AND (#{DealNote.table_name}.source_type = 'Deal')"} }
|
||||
|
||||
|
||||
end
|
||||
7
app/models/deal_process.rb
Normal file
7
app/models/deal_process.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class DealProcess < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
|
||||
belongs_to :deal
|
||||
|
||||
end
|
||||
79
app/models/deal_status.rb
Normal file
79
app/models/deal_status.rb
Normal file
@ -0,0 +1,79 @@
|
||||
class DealStatus < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
has_and_belongs_to_many :projects
|
||||
has_many :deals, :foreign_key => 'status_id', :dependent => :nullify
|
||||
|
||||
acts_as_list
|
||||
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name
|
||||
validates_length_of :name, :maximum => 30
|
||||
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
|
||||
|
||||
def after_save
|
||||
DealStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
|
||||
end
|
||||
|
||||
# Returns the default status for new Deals
|
||||
def self.default
|
||||
find(:first, :conditions =>["is_default=?", true])
|
||||
end
|
||||
|
||||
# Returns an array of all statuses the given role can switch to
|
||||
# Uses association cache when called more than one time
|
||||
def new_statuses_allowed_to(roles, tracker)
|
||||
if roles && tracker
|
||||
role_ids = roles.collect(&:id)
|
||||
new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
# Same thing as above but uses a database query
|
||||
# More efficient than the previous method if called just once
|
||||
def find_new_statuses_allowed_to(roles, tracker)
|
||||
if roles && tracker
|
||||
workflows.find(:all,
|
||||
:include => :new_status,
|
||||
:conditions => { :role_id => roles.collect(&:id),
|
||||
:tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def new_status_allowed_to?(status, roles, tracker)
|
||||
if status && roles && tracker
|
||||
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def color_name
|
||||
return "#" + "%06x" % self.color unless self.color.nil?
|
||||
end
|
||||
|
||||
def color_name=(clr)
|
||||
self.color = clr.from(1).hex
|
||||
end
|
||||
|
||||
|
||||
def <=>(status)
|
||||
position <=> status.position
|
||||
end
|
||||
|
||||
def to_s; name end
|
||||
|
||||
private
|
||||
def check_integrity
|
||||
raise "Can't delete status" if Deal.find(:first, :conditions => ["status_id=?", self.id])
|
||||
end
|
||||
|
||||
# Deletes associated workflows
|
||||
def delete_workflows
|
||||
Workflow.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
|
||||
end
|
||||
end
|
||||
79
app/models/note.rb
Normal file
79
app/models/note.rb
Normal file
@ -0,0 +1,79 @@
|
||||
class Note < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
||||
belongs_to :source, :polymorphic => true
|
||||
|
||||
# added as a quick fix to allow eager loading of the polymorphic association for multiprojects
|
||||
|
||||
validates_presence_of :source, :author, :content
|
||||
|
||||
|
||||
after_create :send_mails
|
||||
|
||||
acts_as_customizable
|
||||
acts_as_attachable :view_permission => :view_contacts,
|
||||
:delete_permission => :edit_contacts
|
||||
|
||||
acts_as_event :title => Proc.new {|o| "#{l(:label_note_for)}: #{o.source.name}"},
|
||||
:type => "issue-note",
|
||||
:url => Proc.new {|o| {:controller => 'notes', :action => 'show', :note_id => o.id }},
|
||||
:description => Proc.new {|o| o.content}
|
||||
|
||||
|
||||
|
||||
|
||||
# :joins => "LEFT JOIN #{Contact.table_name} ON #{Note.table_name}.source_type='Contact' AND #{Contact.table_name}.id = #{Note.table_name}.source_id " +
|
||||
# # "JOIN contacts_projects ON contacts_projects.contact_id = #{Contact.table_name}.id " +
|
||||
# # "JOIN #{Project.table_name} ON contacts_projects.project_id = #{Project.table_name}.id" }
|
||||
# Contact.projects_joins.join(' ') }
|
||||
|
||||
# :joins => "LEFT JOIN #{Deal.table_name} ON #{Note.table_name}.source_type='Deal' AND #{Deal.table_name}.id = #{Note.table_name}.source_id " +
|
||||
# "LEFT JOIN #{Project.table_name} ON #{Deal.table_name}.project_id = #{Project.table_name}.id"}
|
||||
|
||||
cattr_accessor :note_types
|
||||
@@note_types = {:email => 0, :call => 1, :meeting => 2}
|
||||
cattr_accessor :cut_length
|
||||
@@cut_length = 1000
|
||||
|
||||
def self.note_types
|
||||
@@note_types
|
||||
end
|
||||
|
||||
def self.available_authors(prj=nil)
|
||||
options = {}
|
||||
options[:select] = "DISTINCT #{User.table_name}.*"
|
||||
options[:joins] = "JOIN #{Note.table_name} nnnn ON nnnn.author_id = #{User.table_name}.id"
|
||||
options[:order] = "#{User.table_name}.lastname, #{User.table_name}.firstname"
|
||||
prj.nil? ? User.active.find(:all, options) : prj.users.active.find(:all, options)
|
||||
end
|
||||
|
||||
def project
|
||||
self.source.respond_to?(:project) ? self.source.project : nil
|
||||
end
|
||||
|
||||
def editable_by?(usr, prj=nil)
|
||||
prj ||= @project || self.project
|
||||
usr && (usr.allowed_to?(:delete_notes, prj) || (self.author == usr && usr.allowed_to?(:delete_own_notes, prj)))
|
||||
# usr && usr.logged? && (usr.allowed_to?(:edit_notes, project) || (self.author == usr && usr.allowed_to?(:edit_own_notes, project)))
|
||||
end
|
||||
|
||||
def destroyable_by?(usr, prj=nil)
|
||||
prj ||= @project || self.project
|
||||
usr && (usr.allowed_to?(:delete_notes, prj) || (self.author == usr && usr.allowed_to?(:delete_own_notes, prj)))
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def send_mails
|
||||
if self.source.class == Contact && !self.source.is_company
|
||||
parent = Contact.find_by_first_name(self.source.company)
|
||||
end
|
||||
Mailer.deliver_contacts_note_added(self, parent)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
7
app/models/note_custom_field.rb
Normal file
7
app/models/note_custom_field.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class NoteCustomField < CustomField
|
||||
unloadable
|
||||
|
||||
def type_name
|
||||
:label_note_plural
|
||||
end
|
||||
end
|
||||
22
app/models/recently_viewed.rb
Normal file
22
app/models/recently_viewed.rb
Normal file
@ -0,0 +1,22 @@
|
||||
class RecentlyViewed < ActiveRecord::Base
|
||||
unloadable
|
||||
|
||||
RECENTLY_VIEWED_LIMIT = 5
|
||||
|
||||
belongs_to :viewer, :class_name => 'User', :foreign_key => 'viewer_id'
|
||||
belongs_to :viewed, :polymorphic => true
|
||||
|
||||
validates_presence_of :viewed, :viewer
|
||||
|
||||
# after_save :increment_views_count
|
||||
def self.last(limit=RECENTLY_VIEWED_LIMIT, usr=nil)
|
||||
RecentlyViewed.find_all_by_viewer_id(usr || User.current, :limit => limit, :order => "#{RecentlyViewed.table_name}.updated_at DESC").collect{|v| v.viewed}.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def increment_views_count
|
||||
self.increment!(:views_count)
|
||||
end
|
||||
|
||||
end
|
||||
14
app/models/task.rb
Normal file
14
app/models/task.rb
Normal file
@ -0,0 +1,14 @@
|
||||
class Task < ActiveRecord::Base
|
||||
validates_presence_of :source_id, :issue_id, :source_type
|
||||
validates_uniqueness_of :source_id, :scope => [:issue_id, :source_type]
|
||||
|
||||
after_save :send_mails
|
||||
|
||||
private
|
||||
|
||||
def send_mails
|
||||
Mailer.deliver_contacts_issue_connected(Contact.find(self.contact_id), Issue.find(self.issue_id))
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
6
app/views/auto_completes/_tag_list.html.erb
Normal file
6
app/views/auto_completes/_tag_list.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<ul>
|
||||
<% @tags.each do |tag| -%>
|
||||
<%= content_tag 'li', h('%s (%d)' % [tag.name, tag.count]), :name => tag.name %>
|
||||
<% end -%>
|
||||
<%= content_tag 'li', l(:label_add_tag) % @name, :name => @name %>
|
||||
</ul>
|
||||
17
app/views/common/_contact_data.html.erb
Normal file
17
app/views/common/_contact_data.html.erb
Normal file
@ -0,0 +1,17 @@
|
||||
<% actions ||= "" %>
|
||||
<table class="note_data">
|
||||
<tr>
|
||||
<td class="avatar"><%= link_to avatar_to(contact_data, :size => "32"), note_source_url(contact_data), :id => "avatar" %></td>
|
||||
<td class="name">
|
||||
<h4 class="contacts_header">
|
||||
<%= link_to contact_data.name, note_source_url(contact_data) %>
|
||||
</h4>
|
||||
<%= contact_data.info %>
|
||||
</td>
|
||||
<% if !actions.blank? %>
|
||||
<td>
|
||||
<%= actions %>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</table>
|
||||
4
app/views/common/_notes_attachments.html.erb
Normal file
4
app/views/common/_notes_attachments.html.erb
Normal file
@ -0,0 +1,4 @@
|
||||
<% if notes_attachments.any? %>
|
||||
<h3><%= l(:label_attachment_plural) %></h3>
|
||||
<%= render :partial => 'attachments/links', :locals => {:attachments => notes_attachments, :options => {}} %>
|
||||
<% end %>
|
||||
4
app/views/common/_recently_viewed.html.erb
Normal file
4
app/views/common/_recently_viewed.html.erb
Normal file
@ -0,0 +1,4 @@
|
||||
<h3><%= l(:label_recently_viewed) %></h3>
|
||||
<div id="recently_viewed">
|
||||
<%= render :partial => 'common/contact_data', :collection => RecentlyViewed.last(5) %>
|
||||
</div>
|
||||
10
app/views/common/_responsible_user.html.erb
Normal file
10
app/views/common/_responsible_user.html.erb
Normal file
@ -0,0 +1,10 @@
|
||||
<% if responsible_user.assigned_to %>
|
||||
<h3><%= l(:label_assigned_to) %></h3>
|
||||
<div id="responsible_user">
|
||||
<ul>
|
||||
<li>
|
||||
<%= avatar(responsible_user.assigned_to, :size => "16").to_s + link_to_user(responsible_user.assigned_to, :class => 'user').to_s %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
17
app/views/common/_sidebar.html.erb
Normal file
17
app/views/common/_sidebar.html.erb
Normal file
@ -0,0 +1,17 @@
|
||||
<%= call_hook(:view_contacts_sidebar_top) %>
|
||||
|
||||
<h3><%= l(:label_task_plural) %></h3>
|
||||
<%= link_to l(:label_contacts_view_all), { :controller => 'contacts', :action => 'index', :project_id => @project} %>
|
||||
|
|
||||
<% if !(@project && !authorize_for(:deals, :index)) %>
|
||||
<%= link_to l(:label_deal_plural), { :controller => 'deals', :action => 'index', :project_id => @project} %>
|
||||
|
|
||||
<% end %>
|
||||
|
||||
<% if !(@project && !authorize_for(:contacts_tasks, :index)) %>
|
||||
<%= link_to l(:label_issue_plural), { :controller => 'contacts_tasks', :action => 'index', :project_id => @project} %>
|
||||
|
|
||||
<% end %>
|
||||
<%= link_to l(:label_contact_note_plural), { :controller => 'contacts', :action => 'contacts_notes', :project_id => @project} %>
|
||||
|
||||
<%= call_hook(:view_contacts_sidebar_bottom) %>
|
||||
68
app/views/contacts/_attributes.html.erb
Normal file
68
app/views/contacts/_attributes.html.erb
Normal file
@ -0,0 +1,68 @@
|
||||
<div id="attributes">
|
||||
|
||||
<h3><%= if !@contact.is_company then l(:label_contact) else l(:label_company) end %></h3>
|
||||
|
||||
<table class="contact attributes vcard">
|
||||
<%= call_hook(:view_contacts_sidebar_attributes_top) %>
|
||||
<tr>
|
||||
<th class = "name"><%= l(:field_contact_name) %>:</th><td class="name fn <%= "org" if @contact.is_company %>"><%= h @contact.name(:firstname_middlename_lastname) %></td>
|
||||
</tr>
|
||||
<% if !@contact.job_title.blank? %>
|
||||
<tr> <th class = "job_title"><%= !@contact.is_company ? l(:field_contact_job_title) : l(:field_company_field) %>:</th><td class="job_title title"><%= h @contact.job_title %></td></tr>
|
||||
<% end %>
|
||||
<% if !@contact.is_company %>
|
||||
<tr><th class = "company"><%=l(:field_contact_company)%>:</th><td class="company org"><%= h @contact.company %></td></tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<th class = "address"><%= l(:field_contact_address) %>:</th>
|
||||
<td class="address adr"><%= h @contact.address %>
|
||||
<% if !@contact.address.blank? %>
|
||||
<br>
|
||||
<%= link_to l(:label_show_on_map), "http://maps.google.com/maps?f=q&q=#{h @contact.address.gsub("\r\n"," ").gsub("\n"," ")}+(#{h @contact.name})&ie=UTF8&om=1"%>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class = "tel">
|
||||
<th class = "phone"><%= l(:field_contact_phone) %>:</th>
|
||||
<td class = "phones">
|
||||
<% @contact.phones.each do |phone| %>
|
||||
<span class="value"><%= h phone %> <br></span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class = "emails">
|
||||
<th><%= l(:field_contact_email) %>:</th>
|
||||
<td>
|
||||
<% @contact.emails.each do |email| %>
|
||||
<span class="email"><%= mail_to email %> <br></span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class = "website"><%= l(:field_contact_website) %>:</th>
|
||||
<td class="website url"><%= link_to @contact.website, @contact.website_address %></td>
|
||||
</tr>
|
||||
<% if !@contact.skype_name.blank? %>
|
||||
<tr>
|
||||
<th class = "skype"><%= l(:field_contact_skype) %>:</th>
|
||||
<td class="skype"><%=skype_to @contact.skype_name %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if !@contact.birthday.blank? %>
|
||||
<tr> <th class = "birthday"><%= l(:field_birthday) %>:</th><td class="birthday bday" title=<%= "#{format_date(@contact.birthday)}" %>><%= "#{@contact.birthday.day} #{t('date.month_names')[@contact.birthday.month]}"%></td> </tr>
|
||||
<tr> <th class = "age"><%= l(:field_age) %>:</th><td class="ega"><%= @contact.age %></td> </tr>
|
||||
<% end %>
|
||||
|
||||
<% @contact.custom_values.each do |custom_value| %>
|
||||
<% if !custom_value.value.blank? %>
|
||||
<tr> <th class = "custom_field"><%= custom_value.custom_field.name%>:</th><td> <%=h show_value(custom_value) %></td> </tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<tr><th class="author"><%=l(:label_assigned_to)%>:</th><td class="author"><%= avatar(@contact.assigned_to, :size => "14") %><%= link_to_user(@contact.assigned_to) %></td></tr>
|
||||
|
||||
<%= call_hook(:view_contacts_sidebar_attributes_bottom) %>
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
10
app/views/contacts/_employees.html.erb
Normal file
10
app/views/contacts/_employees.html.erb
Normal file
@ -0,0 +1,10 @@
|
||||
<% if @contact.is_company %>
|
||||
<div id="employee">
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized l(:label_add_employee), {:controller => 'contacts', :action => 'new', :project_id => @project, :contact => {:company => @contact.name}} %>
|
||||
</div>
|
||||
<h3><%= l(:label_company_employees) %></h3>
|
||||
<%= render :partial => 'common/contact_data', :collection => @contact.employees %>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
87
app/views/contacts/_form.html.erb
Normal file
87
app/views/contacts/_form.html.erb
Normal file
@ -0,0 +1,87 @@
|
||||
<%= error_messages_for 'contact' %>
|
||||
<!-- {:onclick=>"Element.show('edit_tags_form'); Element.hide('last_name'); return false;"} -->
|
||||
|
||||
|
||||
<div class = "box tabular" id="contact_data">
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
function togglePerson(element) {
|
||||
if (element.checked) {
|
||||
Element.hide('person_data');
|
||||
$('job_title').firstChild.innerHTML='<%= l(:field_company_field) %>';
|
||||
$('first_name').firstChild.innerHTML='<%= l(:field_company_name) %>' + '<span class="required"> *</span>';
|
||||
|
||||
} else {
|
||||
Element.show('person_data');
|
||||
$('job_title').firstChild.innerHTML='<%= l(:field_contact_job_title) %>';
|
||||
$('first_name').firstChild.innerHTML='<%= l(:field_contact_first_name) %>' + '<span class="required"> *</span>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<p class="avatar" id="watchers">
|
||||
<%= avatar_to(@contact, :size => "64", :style => "vertical-align: middle;") %>
|
||||
<%= link_to image_tag('delete.png'), {:controller => 'attachments', :action => 'destroy', :id => @contact.avatar},
|
||||
:confirm => l(:text_are_you_sure),
|
||||
:method => :post,
|
||||
:class => 'delete',
|
||||
:style => "vertical-align: middle;",
|
||||
:title => l(:button_delete) unless @contact.avatar.blank? %>
|
||||
</p>
|
||||
<p><%= label_tag l(:field_contact_avatar) %> <%= file_field_tag 'contact_avatar[file]', :size => 30, :id => nil -%> </p>
|
||||
<p><%= f.check_box(:is_company, :label => l(:field_contact_is_company), :onclick => "togglePerson(this)" ) %></p>
|
||||
<p id="first_name"><%= f.text_field :first_name, :label => !@contact.is_company ? l(:field_contact_first_name) : l(:field_company_name), :required => true, :size => 80%></p>
|
||||
<% if @contact.is_company %>
|
||||
<div id="person_data" style="display: none;">
|
||||
<% else %>
|
||||
<div id="person_data">
|
||||
<% end %>
|
||||
<p><%= f.text_field 'middle_name', :label=>l(:field_contact_middle_name) %></p>
|
||||
<p><%= f.text_field :last_name, :label=>l(:field_contact_last_name), :id => 'contact_last_name' %></p>
|
||||
<p><%= f.text_field 'company', :label=>l(:field_contact_company) -%></p>
|
||||
<p><%= f.text_field :birthday, :size => 12 %><%= calendar_for('contact_birthday') %> </p>
|
||||
</div>
|
||||
<p id="job_title"><%= f.text_field :job_title, :label => !@contact.is_company ? l(:field_contact_job_title) : l(:field_company_field) %></p>
|
||||
<p><%= f.text_area 'address', :label=>l(:field_contact_address), :rows => 5 -%></p>
|
||||
|
||||
<div id="phones_fields">
|
||||
<p>
|
||||
<%= f.text_field :phone, :label=>l(:field_contact_phone), :size => 80 -%>
|
||||
<br>
|
||||
<em><%= l(:text_comma_separated) %></em>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<%= f.text_field 'email', :label=>l(:field_contact_email), :size => 80 -%>
|
||||
<br>
|
||||
<em><%= l(:text_comma_separated) %></em>
|
||||
</p>
|
||||
|
||||
<p><%= f.text_field 'website', :label=>l(:field_contact_website) -%></p>
|
||||
<p><%= f.text_field 'skype_name', :label=>l(:field_contact_skype) -%></p>
|
||||
|
||||
<% @contact.custom_field_values.each do |value| %>
|
||||
<p>
|
||||
<%= custom_field_tag_with_label :contact, value %>
|
||||
</p>
|
||||
<% end -%>
|
||||
|
||||
<p class = "notes"><%= f.text_area :background , :cols => 80, :rows => 8, :class => 'wiki-edit', :label=>l(:field_contact_background) %></p>
|
||||
<%= wikitoolbar_for 'contact_background' %>
|
||||
|
||||
<%= render :partial => "contacts_tags/tags_form" %>
|
||||
|
||||
<% if @project %>
|
||||
<p><%= f.select :assigned_to_id, (@project.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true, :label => l(:label_assigned_to) %></p>
|
||||
<% end %>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
22
app/views/contacts/_form_tags.html.erb
Normal file
22
app/views/contacts/_form_tags.html.erb
Normal file
@ -0,0 +1,22 @@
|
||||
<div id="tags_data">
|
||||
<span class="tags">
|
||||
<%= render :partial => 'contacts/tags_item', :collection => @contact.tags, :locals => {:is_note => false} %>
|
||||
</span>
|
||||
<% if authorize_for('contacts', 'edit_tags') %>
|
||||
<span class="contextual">
|
||||
<%= link_to l(:label_edit_tags), {}, :onclick => "Element.show('edit_tags_form'); Element.hide('tags_data'); return false;", :id => 'edit_tags_link' %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="box" id="edit_tags_form" style="display:none;">
|
||||
<% form_tag( {:controller => 'contacts',
|
||||
:action => 'edit_tags',
|
||||
:project_id => @project,
|
||||
:id => @contact },
|
||||
:multipart => true ) do %>
|
||||
|
||||
<%= render :partial => "contacts_tags/tags_form" %>
|
||||
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<%= link_to l(:button_cancel), {}, :onclick => "Element.hide('edit_tags_form'); Element.show('tags_data'); return false;" %>
|
||||
<% end %></div>
|
||||
45
app/views/contacts/_list.html.erb
Normal file
45
app/views/contacts/_list.html.erb
Normal file
@ -0,0 +1,45 @@
|
||||
<% form_tag({}) do -%>
|
||||
<%= hidden_field_tag 'back_url', url_for(params) %>
|
||||
<%= hidden_field_tag 'project_id', @project.id if @project %>
|
||||
<% unless @contacts.empty? %>
|
||||
<table class="contacts index">
|
||||
<tbody>
|
||||
<% @contacts.each do |contact| %>
|
||||
<tr class="hascontextmenu">
|
||||
<td class="checkbox">
|
||||
<%= check_box_tag "selected_contacts[]", contact.id, false, :onclick => "toggleContact(event, this);" %>
|
||||
</td>
|
||||
<td class="avatar">
|
||||
<%= link_to avatar_to(contact, :size => "32"), {:controller => 'contacts', :action => 'show', :project_id => @project, :id => contact.id}, :id => "avatar" %>
|
||||
</td>
|
||||
<td class="name">
|
||||
<h1><%= link_to contact.name, contact_url(contact) %></h1>
|
||||
<h2>
|
||||
<%= link_to h(contact.website), contact.website_address, :only_path => true unless !contact.is_company %>
|
||||
<%= mail_to contact.emails.first unless contact.is_company%>
|
||||
<div><%= contact.phones.first %></div>
|
||||
</h2>
|
||||
</td>
|
||||
<td class="info">
|
||||
<div class="title_and_company" >
|
||||
<%= h contact.job_title %>
|
||||
<% if !contact.is_company %>
|
||||
<%= " #{l(:label_at_company)} " unless (contact.job_title.blank? or contact.company.blank?) %>
|
||||
<%= h contact.company %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<%= render :partial => "tags_item", :collection => contact.tags, :locals => {:is_note => false} %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<%= contacts_paginator @contacts_pages, :params => {:project_id => params[:project_id]} if @contacts_pages %>
|
||||
<% else %>
|
||||
<p class="nodata"><%=l(:label_no_data)%></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= context_menu url_for( {:controller => "contacts", :action => "context_menu"} )%>
|
||||
18
app/views/contacts/_name_observer.html.erb
Normal file
18
app/views/contacts/_name_observer.html.erb
Normal file
@ -0,0 +1,18 @@
|
||||
<%= observe_field("contact_first_name",
|
||||
:frequency => 1,
|
||||
:update => 'duplicates',
|
||||
:url => {:controller => 'contacts_duplicates', :action => 'duplicates', :project_id => @project, :contact_id => @contact},
|
||||
:with => "$('contact_form').serialize()") %>
|
||||
|
||||
<%= observe_field("contact_middle_name",
|
||||
:frequency => 1,
|
||||
:update => 'duplicates',
|
||||
:url => {:controller => 'contacts_duplicates', :action => 'duplicates', :project_id => @project, :contact_id => @contact},
|
||||
:with => "$('contact_form').serialize()") %>
|
||||
|
||||
<%= observe_field("contact_last_name",
|
||||
:frequency => 1,
|
||||
:update => 'duplicates',
|
||||
:url => {:controller => 'contacts_duplicates', :action => 'duplicates', :project_id => @project, :contact_id => @contact},
|
||||
:with => "$('contact_form').serialize()") %>
|
||||
|
||||
21
app/views/contacts/_tags_cloud.html.erb
Normal file
21
app/views/contacts/_tags_cloud.html.erb
Normal file
@ -0,0 +1,21 @@
|
||||
<div id="tags">
|
||||
<span id="single_tags" <%= "style='display: none;'" if (@tag && @tag.count > 1) %> >
|
||||
<span class="contextual" <%= "style='display: none;'" if !@tag %>>
|
||||
<%= link_to l(:label_multiple_tags_mode), {}, :onclick => "$('multiple_tags').toggle(); $('single_tags').toggle(); return false;", :id => 'edit_tags_link' %>
|
||||
</span>
|
||||
|
||||
<h3><%= l(:label_tags_plural) %></h3>
|
||||
<%= render :partial => "tags_item", :collection => @tags, :locals => {:is_note => false, :multiple => false, :show_selected => true} %>
|
||||
</span>
|
||||
|
||||
<span id="multiple_tags" <%= "style='display: none;'" if !@tag || (@tag && @tag.count < 2) %>>
|
||||
<span class="contextual">
|
||||
<%= link_to l(:label_single_tag_mode), {}, :onclick => "$('multiple_tags').toggle(); $('single_tags').toggle(); return false;", :id => 'edit_tags_link' %>
|
||||
</span>
|
||||
|
||||
<h3><%= l(:label_multi_tags_plural) %></h3>
|
||||
<%= render :partial => "tags_item", :collection => @tags, :locals => {:is_note => false, :multiple => true, :show_selected => true} %>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
28
app/views/contacts/_tags_item.html.erb
Normal file
28
app/views/contacts/_tags_item.html.erb
Normal file
@ -0,0 +1,28 @@
|
||||
<%
|
||||
multiple = false if multiple.blank?
|
||||
show_selected = false if show_selected.blank?
|
||||
|
||||
current_tags = ActsAsTaggableOn::TagList.from(params[:tag])
|
||||
|
||||
tag_url = multiple ? [params[:tag], tags_item.name].join(', ') : tags_item.name
|
||||
|
||||
color = tags_item.color_name
|
||||
html_options = {}
|
||||
html_options[:id] = "tag_#{tags_item.id}"
|
||||
html_options[:style] = "background-color: #{color}"
|
||||
|
||||
if current_tags.include?(tags_item.name) and show_selected
|
||||
tag_url = multiple ? current_tags.remove(tags_item.name).to_s : tags_item.name
|
||||
html_options.delete(:style)
|
||||
html_options[:class] = "selected"
|
||||
end
|
||||
%>
|
||||
|
||||
<span class="tag" >
|
||||
<%- if !is_note -%>
|
||||
<%= link_to tags_item.name + "#{"(" + tags_item.count.to_s + ")" if tags_item.count > 0}", {:controller => "contacts", :action => "index", :project_id => @project, :tag => tag_url}, html_options %>
|
||||
<%- else -%>
|
||||
<%= link_to tags_item.name, {:controller => "contacts", :action => "contacts_notes", :project_id => @project, :tag => tags_item.name}, html_options %>
|
||||
<%- end -%>
|
||||
</span>
|
||||
|
||||
109
app/views/contacts/bulk_edit.html.erb
Normal file
109
app/views/contacts/bulk_edit.html.erb
Normal file
@ -0,0 +1,109 @@
|
||||
<h2><%= l(:label_bulk_edit_selected_contacts) %></h2>
|
||||
|
||||
|
||||
<div class="box" id="duplicates">
|
||||
<ul>
|
||||
<% @contacts.each do |contact| %>
|
||||
<li>
|
||||
<%= avatar_to contact, :size => "16" %>
|
||||
<%= link_to_source contact %>,
|
||||
<%= h contact.job_title %>
|
||||
<%= " #{l(:label_at_company)} " unless (contact.job_title.blank? or contact.company.blank?) %>
|
||||
<% if contact.contact_company %>
|
||||
<%= link_to contact.contact_company.name, {:controller => 'contacts', :action => 'show', :id => contact.contact_company.id } %>
|
||||
<% else %>
|
||||
<%= h contact.company %>
|
||||
<% end %>
|
||||
<%= "(#{l(:field_contact_tag_names)}: #{contact.tag_list})" if contact.tags.any? %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<% form_tag(:action => 'bulk_update') do %>
|
||||
<%= @contacts.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
|
||||
<div class="box tabular">
|
||||
<fieldset class="attributes">
|
||||
<legend><%= l(:label_change_properties) %></legend>
|
||||
|
||||
<p>
|
||||
<label><%= l(:field_company) %></label>
|
||||
<%= text_field_tag('contact[company]', '') %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label><%= l(:field_contact_job_title) %>/<%= l(:field_company_field) %></label>
|
||||
<%= text_field_tag('contact[job_title]', '') %>
|
||||
</p>
|
||||
|
||||
<% @contacts.first.custom_field_values.each do |value| %>
|
||||
<p>
|
||||
<% value.value = '' %>
|
||||
<%= custom_field_tag_with_label :contact, value %>
|
||||
</p>
|
||||
<% end -%>
|
||||
|
||||
|
||||
<p>
|
||||
<label><%= l(:label_assigned_to) %></label>
|
||||
<%= select_tag('contact[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_nobody), :value => 'none') +
|
||||
options_from_collection_for_select(@assignables, :id, :name)) %>
|
||||
</p>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="attributes">
|
||||
<legend><%= l(:label_tags_plural) %></legend>
|
||||
|
||||
<div>
|
||||
<p id="add_tags">
|
||||
<label><%= l(:field_add_tags) %></label>
|
||||
<%= text_field_tag 'add_tag_list', '', :label => :field_contact_tag_names, :size => 10, :class => 'hol', :style => "display: none;" %>
|
||||
</p>
|
||||
<div id="add_tag_candidates" class="autocomplete"></div>
|
||||
<%= javascript_tag "observeTagsField('#{url_for(:controller => 'auto_completes', :action => 'contact_tags', :project_id => @project)}', 'add_tag_list', 'add_tag_candidates')" %>
|
||||
</div>
|
||||
<div>
|
||||
<p id="delete_tags">
|
||||
<label><%= l(:field_delete_tags) %></label>
|
||||
<%= text_field_tag 'delete_tag_list', '', :label => :field_contact_tag_names, :size => 10, :class => 'hol', :style => "display: none;" %>
|
||||
</p>
|
||||
<div id="delete_tag_candidates" class="autocomplete"></div>
|
||||
<%= javascript_tag "observeTagsField('#{url_for(:controller => 'auto_completes', :action => 'contact_tags', :project_id => @project)}', 'delete_tag_list', 'delete_tag_candidates')" %>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<% if @add_projects.any? %>
|
||||
<fieldset class="attributes">
|
||||
<legend><%= l(:label_project_plural) %></legend>
|
||||
<p>
|
||||
<label><%= l(:label_add_into) %></label>
|
||||
<%= select_tag 'add_projects_list[]', content_tag('option', l(:label_no_change_option), :value => '', :selected => 'selected') + project_tree_options_for_select(@add_projects), :multiple => false %>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label><%= l(:label_delete_from) %></label>
|
||||
<%= select_tag 'delete_projects_list[]', content_tag('option', l(:label_no_change_option), :value => '', :selected => 'selected') + project_tree_options_for_select(@add_projects), :multiple => false %>
|
||||
</p>
|
||||
|
||||
|
||||
</fieldset>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
<fieldset><legend><%= l(:field_notes) %></legend>
|
||||
<%= text_area_tag 'note[content]', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %>
|
||||
<%= wikitoolbar_for 'note_content' %>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<p><%= submit_tag l(:button_submit) %></p>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
87
app/views/contacts/contacts_notes.html.erb
Normal file
87
app/views/contacts/contacts_notes.html.erb
Normal file
@ -0,0 +1,87 @@
|
||||
<div class="filters">
|
||||
<% if !@tag %>
|
||||
|
||||
<% form_tag(params, :id => "query_form") do %>
|
||||
<%= hidden_field_tag('project_id', @project.to_param) if @project %>
|
||||
<h2>
|
||||
<span class="scope_title">
|
||||
<%= l(:label_contact_note_plural) %>
|
||||
</span>
|
||||
<span class="live_search">
|
||||
<%= label_tag :search_note, l(:label_search), :id => "search_overlabel" %>
|
||||
|
||||
<%= text_field_tag(:search_note, params[:search_note], :autocomplete => "off", :size => "35", :class => "live_search_field", :onfocus => "Element.hide('search_overlabel'); return false;", :onblur => "if (this.value == '') {Element.show('search_overlabel');}" ) %>
|
||||
|
||||
<%= observe_field("search_note",
|
||||
:frequency => 1,
|
||||
:update => 'contacts_notes',
|
||||
:url => {:controller => 'contacts', :action => 'contacts_notes', :project_id => @project },
|
||||
:with => "Form.serialize('query_form')") %>
|
||||
|
||||
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<% if false %>
|
||||
<fieldset id="filters" class="collapsible collapsed">
|
||||
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
|
||||
<div style="display: none;">
|
||||
<p>
|
||||
<%= label_tag l(:label_author) + " " %>
|
||||
<%= select_tag :note_author_id, options_for_select(Note.available_authors(@project).collect{|u| [u.name, u.id]}.insert(0, [""]), params[:note_author_id]) %>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
<p class="buttons hide-when-print">
|
||||
|
||||
<%= link_to_remote l(:button_apply),
|
||||
{ :url => {},
|
||||
:update => "deal_list",
|
||||
:with => "Form.serialize('query_form')"
|
||||
}, :class => 'icon icon-checked' %>
|
||||
|
||||
<%= link_to l(:button_clear),
|
||||
{:project_id => @project },
|
||||
:method => :get,
|
||||
:update => "deal_list",
|
||||
:class => 'icon icon-reload' %>
|
||||
</p>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
<% else %>
|
||||
<h2 class="scope_title"><%= "#{l(:label_contact_tag)}(#{@notes_pages.item_count}): #{render(:partial => "tags_item", :collection => @tag, :locals => {:is_note => false} )}" %> </h2>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="contacts_notes">
|
||||
<%= render :partial => 'notes/notes_list' %>
|
||||
</div>
|
||||
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
|
||||
|
||||
<h3><%= l(:label_tags_plural) %></h3>
|
||||
<div id="tags">
|
||||
<%= render :partial => "tags_item", :collection => @tags, :locals => {:is_note => true, :multiple => false} %>
|
||||
</div>
|
||||
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% content_for(:header_tags) do %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
|
||||
28
app/views/contacts/context_menu.html.erb
Normal file
28
app/views/contacts/context_menu.html.erb
Normal file
@ -0,0 +1,28 @@
|
||||
<ul>
|
||||
|
||||
<% if !@contact.nil? %>
|
||||
<li><%= context_menu_link l(:button_edit), {:controller => 'contacts', :action => 'edit', :id => @contact, :project => @project}, :class => 'icon-edit', :disabled => !@can[:edit] %></li>
|
||||
<% if User.current.logged? %>
|
||||
<li><%= watcher_link(@contact, User.current) %></li>
|
||||
<% end %>
|
||||
|
||||
<% if !@project.nil? %>
|
||||
<li><%= context_menu_link l(:label_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project, :contact_id => @contact},
|
||||
:class => 'icon-add-deal', :disabled => !@can[:create_deal] %></li>
|
||||
<% if @contact.is_company? %>
|
||||
<li><%= context_menu_link l(:label_add_employee), {:controller => 'contacts', :action => 'new', :project_id => @project, :contact => {:company => @contact.name}},
|
||||
:class => 'icon-add-employee', :disabled => !@can[:edit] %></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<li><%= context_menu_link l(:button_edit), {:controller => 'contacts', :action => 'bulk_edit', :ids => @contacts.collect(&:id)},
|
||||
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
|
||||
<% end %>
|
||||
|
||||
<li><%= context_menu_link l(:label_send_mail), {:controller => 'contacts', :action => 'edit_mails', :ids => @contacts.collect(&:id), :project_id => @project}, :class => 'icon-email', :disabled => !@can[:send_mails] %></li>
|
||||
|
||||
|
||||
<li><%= context_menu_link l(:button_delete), {:controller => 'contacts', :action => 'bulk_destroy', :ids => @contacts.collect(&:id), :project_id => @project},
|
||||
:method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
|
||||
</ul>
|
||||
|
||||
25
app/views/contacts/edit.html.erb
Normal file
25
app/views/contacts/edit.html.erb
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized l(:label_contact_new), {:controller => 'contacts', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<h2><%= l(:label_contact_edit_information) %></h2>
|
||||
|
||||
<% labelled_form_for :contact, @contact,
|
||||
:url => {:action => 'update', :project_id => @project, :id => @contact},
|
||||
:html => { :multipart => true, :method => :put, :id => "contact_form" } do |f| %>
|
||||
<%= render :partial => 'form', :locals => {:f => f} %>
|
||||
<%= render :partial => 'name_observer' %>
|
||||
<%= submit_tag l(:button_save) -%>
|
||||
<% end -%>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
<%= render :partial => 'contacts_duplicates/duplicates' %>
|
||||
<%= render :partial => 'contacts_projects/related' %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
65
app/views/contacts/edit_mails.html.erb
Normal file
65
app/views/contacts/edit_mails.html.erb
Normal file
@ -0,0 +1,65 @@
|
||||
<h2><%= l(:label_bulk_send_mail_selected_contacts) %></h2>
|
||||
|
||||
|
||||
<div class="box" id="duplicates">
|
||||
<ul>
|
||||
<% @contacts.each do |contact| %>
|
||||
<li>
|
||||
<%= avatar_to contact, :size => "16" %>
|
||||
<%= link_to_source contact %>
|
||||
<%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
|
||||
- <%= contact.emails.first %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<% form_for(:email_message, :url => {:action => 'send_mails'}, :html => {:multipart => true, :id => 'message-form'}) do %>
|
||||
<%= @contacts.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
|
||||
|
||||
<div class="box tabular">
|
||||
<p>
|
||||
<label><%= l(:field_mail_from) %></label>
|
||||
<%= text_field_tag('from', User.current.mail, :id => "email", :size => 30) %>
|
||||
</p>
|
||||
|
||||
<!-- <p>
|
||||
<label><%= l(:setting_bcc_recipients) %></label>
|
||||
<%= text_field_tag('bcc', User.current.mail, :id => "email", :size => 30) %>
|
||||
</p> -->
|
||||
|
||||
<p>
|
||||
<label><%= l(:field_subject) %></label>
|
||||
<%= text_field_tag('subject', '', :id => "subject", :size => 100) %>
|
||||
</p>
|
||||
<p>
|
||||
<label><%= l(:field_message) %></label>
|
||||
<%= text_area_tag 'message-content', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %>
|
||||
<em><%= l(:text_email_macros, :macro => "%%NAME%%, %%LAST_NAME%%, %%MIDDLE_NAME%%, %%FULL_NAME%%, %%COMPANY%%, %%DATE%%, %%[Custom field]%%") %></em>
|
||||
</p>
|
||||
<%= wikitoolbar_for 'message-content' %>
|
||||
|
||||
<p id="attachments_form"><%= label_tag('attachments[1][file]', l(:label_attachment_plural))%><%= render :partial => 'attachments/form' %></p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<%= submit_tag l(:button_submit) %>
|
||||
<%= link_to_remote l(:label_preview),
|
||||
{ :url => { :controller => 'contacts', :action => 'preview_email' },
|
||||
:method => 'post',
|
||||
:update => 'preview',
|
||||
:with => "Form.serialize('message-form')",
|
||||
:complete => "Element.scrollTo('preview')"
|
||||
}, :accesskey => accesskey(:preview) %>
|
||||
|
||||
</p>
|
||||
|
||||
<% end %>
|
||||
|
||||
<div id="preview" class="wiki"></div>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
41
app/views/contacts/index.api.rsb
Normal file
41
app/views/contacts/index.api.rsb
Normal file
@ -0,0 +1,41 @@
|
||||
api.array :contacts, api_meta(:total_count => @contacts_count, :offset => @offset, :limit => @limit) do
|
||||
@contacts.each do |contact|
|
||||
api.contact do
|
||||
api.id contact.id
|
||||
api.is_company contact.is_company
|
||||
|
||||
api.first_name contact.first_name
|
||||
api.last_name contact.last_name
|
||||
api.middle_name contact.middle_name
|
||||
api.company contact.company
|
||||
api.address contact.address
|
||||
api.website contact.website
|
||||
api.skype_name contact.skype_name
|
||||
api.birthday contact.birthday
|
||||
api.job_title contact.job_title
|
||||
api.background contact.background
|
||||
api.author(:id => contact.author_id, :name => contact.author.name) unless contact.author.nil?
|
||||
api.assigned_to(:id => contact.assigned_to_id, :name => contact.assigned_to.name) unless contact.assigned_to.nil?
|
||||
|
||||
api.array :phones do
|
||||
contact.phones.each do |phone|
|
||||
api.phone phone
|
||||
end
|
||||
end if contact.phones.any?
|
||||
|
||||
api.array :emails do
|
||||
contact.emails.each do |email|
|
||||
api.email email
|
||||
end
|
||||
end if contact.emails.any?
|
||||
|
||||
api.tags contact.tag_list
|
||||
|
||||
|
||||
render_api_custom_values contact.custom_field_values, api
|
||||
|
||||
api.created_on contact.created_on
|
||||
api.updated_on contact.updated_on
|
||||
end
|
||||
end
|
||||
end
|
||||
82
app/views/contacts/index.html.erb
Normal file
82
app/views/contacts/index.html.erb
Normal file
@ -0,0 +1,82 @@
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized l(:label_contact_new), {:controller => 'contacts', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<h2 class="contacts_header">
|
||||
<span id="select_scope" style="display:none;">
|
||||
|
||||
<% form_tag(params, :method => :get) do %>
|
||||
<%= select_tag :query, contacts_filters_for_select(params[:query]), :onchange => "if (this.value=='-1') {this.value='#{params[:query]}' ;Element.toggle('select_scope'); Element.toggle('scope_header');} else {this.form.submit();}" %>
|
||||
<% end %>
|
||||
</span>
|
||||
|
||||
<span id='scope_header' class="scope_title">
|
||||
<%= link_to "#{contacts_filter_name(params[:query])}(#{@contacts_count})", {}, :onclick => "Element.toggle('select_scope'); Element.toggle('scope_header'); return false;" %>
|
||||
</span>
|
||||
|
||||
<% if !@tag %>
|
||||
<span class="live_search">
|
||||
<%= label_tag :search, l(:label_search), :id => "search_overlabel" %>
|
||||
<%= text_field_tag(:search, params[:search], :autocomplete => "off", :size => "35", :class => "live_search_field", :onfocus => "Element.hide('search_overlabel'); return false;", :onblur => "if (this.value == '') {Element.show('search_overlabel');}" ) %>
|
||||
<!-- , :onfocus => "if (this.value == 'Поиск') {this.value = '';}", :onblur => "if (this.value == '') {this.value ='Поиск'; this.style.color='#aaa'}" -->
|
||||
<%= observe_field("search",
|
||||
:frequency => 2,
|
||||
:update => 'contact_list',
|
||||
:url => {:controller => 'contacts', :action => 'index', :project_id => @project },
|
||||
:with => "search") %>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<span class="tags">
|
||||
<%= render(:partial => "tags_item", :collection => @tag, :locals => {:is_note => true} ) %>
|
||||
</span>
|
||||
|
||||
</h2>
|
||||
|
||||
<% unless true %>
|
||||
|
||||
<fieldset id="filters" class="collapsible <%= 'collapsed' if params[:deal_status_id].blank? %>">
|
||||
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
|
||||
<div style="<%= 'display: none;' if params[:deal_status_id].blank? %>">
|
||||
<%= select_tag :deal_status_id, options_from_collection_for_select(@project.assignable_users, :id, :name), :onchange => "this.form.submit();" %>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
<div id="contact_list">
|
||||
<%= render :partial => 'list' %>
|
||||
</div>
|
||||
|
||||
<% other_formats_links do |f| %>
|
||||
<%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %>
|
||||
<%= f.link_to 'CSV', :url => params %>
|
||||
<%- if Gem.available?('vpim') -%>
|
||||
<%= f.link_to 'VCF', :url => params %>
|
||||
<%- end -%>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% html_title l(:label_contact_plural) %>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
<%= render :partial => 'tags_cloud' %>
|
||||
<%= render :partial => 'notes/last_notes', :object => @last_notes %>
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
|
||||
<%= call_hook(:view_contacts_sidebar_contacts_list_bottom) %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% content_for(:header_tags) do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => l(:label_contact_plural)) %>
|
||||
|
||||
<% end %>
|
||||
|
||||
20
app/views/contacts/new.html.erb
Normal file
20
app/views/contacts/new.html.erb
Normal file
@ -0,0 +1,20 @@
|
||||
<h2><%= l(:label_contact_new) %></h2>
|
||||
|
||||
<% labelled_form_for :contact, @contact, :url => {:action => 'create', :project_id => @project}, :html => { :multipart => true, :id => 'contact_form'} do |f| %>
|
||||
<%= render :partial => 'form', :locals => {:f => f} %>
|
||||
<%= render :partial => 'name_observer' %>
|
||||
<%= submit_tag l(:button_save) -%>
|
||||
<% end -%>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
<%= render :partial => 'contacts_duplicates/duplicates' %>
|
||||
<%= render :partial => 'contacts_vcf/load' %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
83
app/views/contacts/show.api.rsb
Normal file
83
app/views/contacts/show.api.rsb
Normal file
@ -0,0 +1,83 @@
|
||||
api.contact do
|
||||
api.id @contact.id
|
||||
api.is_company @contact.is_company
|
||||
|
||||
api.firstname @contact.first_name
|
||||
api.lastname @contact.last_name
|
||||
api.middlename @contact.middle_name
|
||||
api.company @contact.company
|
||||
api.address @contact.address
|
||||
api.website @contact.website
|
||||
api.skype_name @contact.skype_name
|
||||
api.birthday @contact.birthday
|
||||
api.job_title @contact.job_title
|
||||
api.background @contact.background
|
||||
api.author(:id => @contact.author_id, :name => @contact.author.name) unless @contact.author.nil?
|
||||
api.assigned_to(:id => @contact.assigned_to_id, :name => @contact.assigned_to.name) unless @contact.assigned_to.nil?
|
||||
|
||||
api.array :phones do
|
||||
@contact.phones.each do |phone|
|
||||
api.phone phone
|
||||
end
|
||||
end if @contact.phones.any?
|
||||
|
||||
api.array :emails do
|
||||
@contact.emails.each do |email|
|
||||
api.email email
|
||||
end
|
||||
end if @contact.emails.any?
|
||||
|
||||
api.tags @contact.tag_list
|
||||
|
||||
|
||||
render_api_custom_values @contact.custom_field_values, api
|
||||
|
||||
api.created_on @contact.created_on
|
||||
api.updated_on @contact.updated_on
|
||||
|
||||
|
||||
api.array :projects do
|
||||
@contact.projects.each do |project|
|
||||
api.project(:id => project.id, :name => project.name)
|
||||
end
|
||||
end if @contact.projects.present?
|
||||
|
||||
if authorize_for(:notes, :show)
|
||||
api.array :notes do
|
||||
@contact.notes.each do |note|
|
||||
api.note do
|
||||
api.id note.id
|
||||
api.content note.content
|
||||
api.author(:id => note.author_id, :name => note.author.name) unless note.author.nil?
|
||||
api.created_on note.created_on
|
||||
api.updated_on note.updated_on
|
||||
end
|
||||
end
|
||||
end if @contact.notes.present? && User.current.allowed_to?(:view_contacts, @project)
|
||||
end
|
||||
|
||||
api.array :employees do
|
||||
@contact.employees.each do |employee|
|
||||
api.employee(:id => employee.id, :name => employee.name )
|
||||
end
|
||||
end if @contact.employees.present?
|
||||
|
||||
api.array :deals do
|
||||
(@contact.related_deals + @contact.deals).each do |deal|
|
||||
api.deal do
|
||||
api.id deal.id
|
||||
api.price deal.price
|
||||
api.currency deal.currency
|
||||
api.price_type deal.price_type
|
||||
api.name deal.name
|
||||
api.project(:id => deal.project.id, :name => deal.project.name)
|
||||
api.status(:id => deal.status.id, :name => deal.status.name)
|
||||
api.background deal.background
|
||||
api.created_on deal.created_on
|
||||
api.updated_on deal.updated_on
|
||||
end
|
||||
end
|
||||
end if (@contact.related_deals + @contact.deals).present? && User.current.allowed_to?(:view_deals, @project)
|
||||
|
||||
|
||||
end
|
||||
120
app/views/contacts/show.html.erb
Normal file
120
app/views/contacts/show.html.erb
Normal file
@ -0,0 +1,120 @@
|
||||
<%= error_messages_for 'contact', 'note' %>
|
||||
<%= error_messages_for %>
|
||||
|
||||
<% html_title "#{l(:label_contact)} ##{@contact.id}: #{@contact.name}" %>
|
||||
|
||||
<div class="contextual">
|
||||
<% replace_watcher ||= 'watcher' %>
|
||||
<%= link_to l(:label_profile), user_path(@contact.redmine_user), :class => 'icon icon-user' unless @contact.redmine_user.blank? %>
|
||||
<%= link_to(l(:button_create), {:controller => 'users', :action => 'new_from_contact', :contact_id => @contact.id, :id => 'current'}, :class => 'icon icon-user') if (User.current.admin? && @contact.redmine_user.blank? && !@contact.email.blank?) %>
|
||||
<%= link_to_if_authorized l(:label_send_mail), {:controller => 'contacts', :action => 'edit_mails', :ids => [@contact.id], :project_id => @project}, :class => 'icon icon-email' unless @contact.emails.first.blank? %>
|
||||
|
||||
<%= watcher_tag(@contact, User.current, {:id => replace_watcher, :replace => ['watcher','watcher2']}) %>
|
||||
<%= link_to_if_authorized l(:button_edit), {:controller => 'contacts', :action => 'edit', :project_id => @project, :id => @contact}, :class => 'icon icon-edit' unless @contact.nil? %>
|
||||
<%= link_to_if_authorized l(:button_delete), {:controller => 'contacts', :action => 'destroy', :project_id => @project, :id => @contact}, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' unless @contact.nil? %>
|
||||
</div>
|
||||
|
||||
<h2><%= !@contact.is_company ? l(:label_contact) : l(:label_company) %> #<%= @contact.id %></h2>
|
||||
<% unless @contact.nil? -%>
|
||||
<div class="contact details">
|
||||
<table class="subject_header">
|
||||
<tr>
|
||||
<td class="avatar"><%= avatar_to(@contact, :size => "64", :full_size => true) %></td>
|
||||
<td class="name" style="vertical-align: top;">
|
||||
<h1><%= h @contact.name %></h1>
|
||||
<% if !@contact.is_company %>
|
||||
<p>
|
||||
<%= h @contact.job_title %>
|
||||
<%= " #{l(:label_at_company)} " unless (@contact.job_title.blank? or @contact.company.blank?) %>
|
||||
<% if @contact.contact_company %>
|
||||
<%= link_to @contact.contact_company.name, {:controller => 'contacts', :action => 'show', :project_id => @contact.contact_company.project(@project), :id => @contact.contact_company.id } %>
|
||||
<% else %>
|
||||
<%= h @contact.company %>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<%= render :partial => 'form_tags' %>
|
||||
|
||||
</td>
|
||||
<% if @contact.phones.any? || @contact.emails.any? %>
|
||||
<td class="subject_info">
|
||||
<ul>
|
||||
<% if @contact.phones.any? %>
|
||||
<li class="phone icon icon-phone"><%= @contact.phones.first %></li>
|
||||
<% end %>
|
||||
|
||||
<% if @contact.emails.any? %>
|
||||
<li class="email icon icon-email"><%= mail_to @contact.emails.first %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<%= call_hook(:view_contacts_show_details_bottom, :contact => @contact) %>
|
||||
|
||||
<% if authorize_for(:notes, :add_note) %>
|
||||
<hr />
|
||||
<%= render :partial => 'notes/add', :locals => {:note_source => @contact} %>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <% contacts_tabs = [{:name => 'general', :partial => 'common/recently_viewed', :label => :label_note_plural},
|
||||
{:name => 'tags', :partial => 'employees', :label => :label_tags_plural},
|
||||
{:name => 'deal_statuses', :partial => 'attributes', :label => :label_deal_status_plural}
|
||||
] %>
|
||||
|
||||
<%= render_tabs contacts_tabs %> -->
|
||||
|
||||
<% if authorize_for(:notes, :show) %>
|
||||
<div id="comments">
|
||||
<h3><%= l(:label_note_plural) %></h3>
|
||||
<div id="notes">
|
||||
<%= render :partial => 'notes/note_item', :collection => @notes, :locals => {:show_info => @contact.is_company, :note_source => @contact} %>
|
||||
<p class="pagination"><%= pagination_links @notes_pages, :params => {:project_id => params[:project_id]}%></p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% else %>
|
||||
<p class="nodata"><%=l(:label_no_data)%></p>
|
||||
<% end %>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
<%= render :partial => 'attributes' %>
|
||||
<%= call_hook(:view_contacts_sidebar_after_attributes, :contact => @contact) %>
|
||||
<%= render :partial => 'contacts_tasks/related', :locals => {:contact => @contact, :tasks => @open_issues} %>
|
||||
<%= call_hook(:view_contacts_sidebar_after_tasks, :contact => @contact) %>
|
||||
<%= render :partial => 'common/notes_attachments', :object => @contact.notes_attachments %>
|
||||
<%= call_hook(:view_contacts_sidebar_after_notes_attachments, :contact => @contact) %>
|
||||
<%= render :partial => 'deals/related_deals', :object => @contact.all_visible_deals %>
|
||||
<%= render :partial => 'employees' %>
|
||||
<% if !@contact.background.blank? %>
|
||||
<h3><%= l(:label_contact_background_info) %></h3>
|
||||
<div class="wiki"><%= textilizable(@contact, :background) %></div>
|
||||
<% end %>
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% other_formats_links do |f| %>
|
||||
<%= f.link_to 'Atom', :url => params.merge(:key => User.current.rss_key) %>
|
||||
<%- if Gem.available?('vpim') -%>
|
||||
<%= f.link_to 'VCF', :url => params %>
|
||||
<%- end -%>
|
||||
<!-- <%= f.link_to 'PDF', :url => params %> -->
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@contact.name} - ##{@contact.id}") %>
|
||||
|
||||
<% end %>
|
||||
23
app/views/contacts_duplicates/_duplicates.html.erb
Normal file
23
app/views/contacts_duplicates/_duplicates.html.erb
Normal file
@ -0,0 +1,23 @@
|
||||
<div id="duplicates">
|
||||
<% if @contact.duplicates.any? %>
|
||||
<% if !@contact.new_record? %>
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized l(:label_merge_dublicate_plural), {:controller => 'contacts_duplicates', :action => 'index', :project_id => @project, :contact_id => @contact} %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h3><%= l(:label_dublicate_plural) %></h3>
|
||||
<ul class="box">
|
||||
<% @contact.duplicates.each do |contact| %>
|
||||
<li>
|
||||
|
||||
<%= avatar_to contact, :size => "16" %>
|
||||
<%= link_to_source contact %>
|
||||
<%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
59
app/views/contacts_duplicates/index.html.erb
Normal file
59
app/views/contacts_duplicates/index.html.erb
Normal file
@ -0,0 +1,59 @@
|
||||
<%= breadcrumb link_to(@contact.name, note_source_url(@contact)) %>
|
||||
|
||||
<div class="contact_data_header">
|
||||
<table class="note_data">
|
||||
<tr>
|
||||
|
||||
<td class="avatar"><%= link_to avatar_to(@contact, :size => "32"), note_source_url(@contact), :id => "avatar" %> </td>
|
||||
<td class="name">
|
||||
<h2 class="note_title">
|
||||
<%= l(:label_dublicate_for_plural) %>: <%= @contact.name %>
|
||||
</h2>
|
||||
<p>
|
||||
<%= h @contact.job_title %>
|
||||
<%= " #{l(:label_at_company)} " unless (@contact.job_title.blank? or @contact.company.blank?) %>
|
||||
<% if @contact.is_company && @contact.contact_company %>
|
||||
<%= link_to @contact.contact_company.name, note_source_url(@contact.contact_company) %>
|
||||
<% else %>
|
||||
<%= h @contact.company %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<% form_tag({:controller => 'contacts_duplicates', :action => 'merge', :project_id => @project, :contact_id => @contact}) do %>
|
||||
<div class="box" id="duplicates">
|
||||
<%= content_tag('div', l(:notice_merged_warning), :class => "flash warning") %>
|
||||
|
||||
<ul>
|
||||
<% @contact.duplicates.each do |contact| %>
|
||||
<li>
|
||||
<%= radio_button_tag "dublicate_id", contact.id %>
|
||||
<%= avatar_to contact, :size => "16" %>
|
||||
<%= link_to_source contact %>
|
||||
<%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<%= submit_tag l(:label_merge_dublicate_plural) %>
|
||||
<% end %>
|
||||
|
||||
<% html_title "#{l(:label_dublicate_plural)} #{@contact.name}" %>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
1
app/views/contacts_mailer/bulk_mail.text.html.rhtml
Normal file
1
app/views/contacts_mailer/bulk_mail.text.html.rhtml
Normal file
@ -0,0 +1 @@
|
||||
<%= textilizable(@params[:message], :only_path => false).gsub(/¶/, '') %>
|
||||
1
app/views/contacts_mailer/bulk_mail.text.plain.rhtml
Normal file
1
app/views/contacts_mailer/bulk_mail.text.plain.rhtml
Normal file
@ -0,0 +1 @@
|
||||
<%= @params[:message] %>
|
||||
30
app/views/contacts_projects/_related.html.erb
Normal file
30
app/views/contacts_projects/_related.html.erb
Normal file
@ -0,0 +1,30 @@
|
||||
<div id="contact_projects">
|
||||
<div class="contextual">
|
||||
<%= link_to_remote(l(:button_add),
|
||||
:url => {:controller => 'contacts_projects',
|
||||
:action => 'add',
|
||||
:project_id => @project,
|
||||
:contact_id => @contact}) if authorize_for(:contacts, :edit) %>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h3><%= l(:label_project_plural) %> </h3>
|
||||
|
||||
<% unless !(@show_form == "true") %>
|
||||
<% form_remote_tag(:url => {:controller => 'contacts_projects',
|
||||
:action => 'add',
|
||||
:contact_id => @contact,
|
||||
:project_id => @project},
|
||||
:method => :post,
|
||||
:html => {:id => 'add-project-form'}) do %>
|
||||
<p><%= select_tag :new_project_id, project_tree_options_for_select(Project.visible.has_module(:contacts_module).find(:all, :order => 'lft')), :prompt => "--- #{l(:actionview_instancetag_blank_option)} ---" %>
|
||||
|
||||
<%= submit_tag l(:button_add) %>
|
||||
<%= toggle_link l(:button_cancel), 'add-project-form'%></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render_contact_projects_hierarchy @contact.projects %>
|
||||
|
||||
</div>
|
||||
10
app/views/contacts_tags/_tags_form.html.erb
Normal file
10
app/views/contacts_tags/_tags_form.html.erb
Normal file
@ -0,0 +1,10 @@
|
||||
<% fields_for :contact, @contact do |f| -%>
|
||||
<div>
|
||||
<p id="contact_tags">
|
||||
<label for="contact_tag_list"><%= l(:field_contact_tag_names) %></label>
|
||||
<%= f.text_field :tag_list, :label => :field_contact_tag_names, :size => 10, :class => 'hol' %>
|
||||
</p>
|
||||
<div id="contact_tag_candidates" class="autocomplete"></div>
|
||||
<%= javascript_tag "observeContactTagsField('#{url_for(:controller => 'auto_completes', :action => 'contact_tags', :project_id => @project)}')" %>
|
||||
</div>
|
||||
<% end -%>
|
||||
32
app/views/contacts_tags/edit.html.erb
Normal file
32
app/views/contacts_tags/edit.html.erb
Normal file
@ -0,0 +1,32 @@
|
||||
<h2><%= link_to l(:contacts_title), :controller => 'settings', :action => 'plugin', :id => 'contacts', :tab => 'tags' %> » <%=h (l(:label_tag) + ": " + @tag.name) %></h2>
|
||||
|
||||
<% form_tag({:action => 'update', :id => @tag, :tab => 'tags' }, :class => "tabular") do %>
|
||||
<%= error_messages_for 'tag' %>
|
||||
<div class="box">
|
||||
<!--[form:optvalue]-->
|
||||
<p><label for="tag_name"><%=l(:field_name)%></label>
|
||||
<%= text_field 'tag', 'name' %></p>
|
||||
|
||||
<p><label for="tag_color"><%=l(:field_color)%></label>
|
||||
<%= text_field 'tag', 'color_name', :class => "colorpicker" %>
|
||||
<!--[eoform:optvalue]-->
|
||||
</p>
|
||||
|
||||
<script>
|
||||
// var picker = new ColourPicker("picker", "trigger");
|
||||
$$('.colorpicker').each(function(el){var p = new ColorPicker(el)});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<% end %>
|
||||
|
||||
<% form_tag({:action => 'destroy', :id => @tag, :tab => 'tags' }) do %>
|
||||
<%= submit_tag l(:button_delete) %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
1
app/views/contacts_tags/index.html.erb
Normal file
1
app/views/contacts_tags/index.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<h2>ContactsTags#index</h2>
|
||||
28
app/views/contacts_tasks/_attributes.html.erb
Normal file
28
app/views/contacts_tasks/_attributes.html.erb
Normal file
@ -0,0 +1,28 @@
|
||||
<% fields_for "issue" do |ff| %>
|
||||
|
||||
<%= label_tag 'task_subject', l(:field_subject)%> <br>
|
||||
<%= ff.text_field :subject, :style => "width: 98%;" %>
|
||||
|
||||
<p>
|
||||
<%= label_tag :assigned_to_id, l(:field_assigned_to)%> <br>
|
||||
<%= ff.select :assigned_to_id, @project.assignable_users.collect {|m| [m.name, m.id]}, :selected => User.current.id, :include_blank => true %>
|
||||
</p>
|
||||
<%= label_tag 'due_date', l(:field_due_date)%> <br>
|
||||
<%= ff.text_field :due_date, :value => Date.today, :size => 12 %><%= calendar_for('issue_due_date') %><br>
|
||||
|
||||
<p>
|
||||
<%= label_tag :description, l(:field_description)%> <br>
|
||||
<%= ff.text_area :description, :value => "", :rows => 6, :class => 'wiki-edit' , :style => "width: 98%;" %><br>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<%= label_tag :tracker_id, l(:field_tracker)%> <br>
|
||||
<%= ff.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]} %>
|
||||
</p>
|
||||
|
||||
<% end %>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
<%= submit_tag l(:button_add) %>
|
||||
43
app/views/contacts_tasks/_related.html.erb
Normal file
43
app/views/contacts_tasks/_related.html.erb
Normal file
@ -0,0 +1,43 @@
|
||||
<div id="issues">
|
||||
|
||||
<div class="contextual">
|
||||
<%= link_to l(:label_issue_new), {}, :onclick => "Element.show('add_issue'); Element.hide(this); return false;", :id => 'add_task_link' if User.current.allowed_to?(:add_issues, @project) %>
|
||||
</div>
|
||||
|
||||
<h3><%= l(:label_issue_plural) %> </h3>
|
||||
|
||||
|
||||
<%= error_messages_for 'issue' %>
|
||||
|
||||
<div id="add_issue" style="display:none;">
|
||||
<% form_tag({ :controller => "contacts_tasks", :action => "new", :project_id => @project, :contact_id => contact}, :multipart => true, :id => "add_task_form") do %>
|
||||
|
||||
<%= render :partial => 'contacts_tasks/attributes' %>
|
||||
|
||||
<%= link_to l(:button_cancel), {}, :onclick => "Element.hide('add_issue'); Element.show('add_task_link'); return false;" %>
|
||||
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="contact_issues">
|
||||
|
||||
<% if tasks.any? %>
|
||||
<table style="width:100%">
|
||||
<% tasks.each do |issue| %>
|
||||
<tr id=<%="issue_#{issue.id}"%>>
|
||||
<td class="done_checkbox">
|
||||
<%= check_box_tag :close, '', false, :disabled => (issue.assigned_to != User.current), :onclick => "this.disable(); $('issue_#{issue.id}').style.textDecoration='line-through';" + remote_function(:url => {:controller => "contacts_tasks", :action => "close", :contact_id => contact, :project_id => issue.project, :issue_id => issue }, :with => "issue_#{issue.id}") %>
|
||||
</td>
|
||||
|
||||
<td class="issue_subject">
|
||||
<%= link_to issue.subject, :controller => :issues, :action => :show, :id => issue %>
|
||||
</td>
|
||||
<td style="vertical-align: top; text-align: right;"><%= format_date(issue.due_date) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
41
app/views/contacts_tasks/index.html.erb
Normal file
41
app/views/contacts_tasks/index.html.erb
Normal file
@ -0,0 +1,41 @@
|
||||
<% content_for(:header_tags) do %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<h2><%= l(:label_issue_plural) %></h2>
|
||||
<% form_tag({:project_id => @project}, :id => 'query_form') do %>
|
||||
<p>
|
||||
<%= label_tag l(:label_assigned_to) %>
|
||||
<%= select_tag :assigned_to, options_for_select(@users, params[:assigned_to]), :onchange => "this.form.submit();" %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% unless @contacts_issues.empty? %>
|
||||
<table class="index" style="width:100%;">
|
||||
<tbody>
|
||||
<% @contacts_issues.select {|i| !i.closed?}.each do |issue| %>
|
||||
<tr id=<%="issue_#{issue.id}"%>>
|
||||
<td style="vertical-align: top; padding-top: 4px; width: 12px;">
|
||||
<%= check_box_tag :close, '', false, :onclick => "this.disable(); $('issue_#{issue.id}').style.textDecoration='line-through';" + remote_function(:url => {:controller => 'contacts_tasks', :action => "close", :project_id => issue.project, :issue_id => issue }, :with => "issue_#{issue.id}") if issue.assigned_to == User.current %>
|
||||
</td>
|
||||
<td style="vertical-align: top; padding-top: 4px; width:100%;">
|
||||
<%= link_to issue.subject, :controller => :issues, :action => :show, :id => issue %>
|
||||
<strong><%= "(#{link_to_source issue.contacts.first})" %></strong>
|
||||
</td>
|
||||
<td style="vertical-align: top; text-align: right; width: 100px;"><%= format_date(issue.due_date) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="nodata"><%=l(:label_no_data)%></p>
|
||||
<% end %>
|
||||
|
||||
16
app/views/contacts_vcf/_load.html.erb
Normal file
16
app/views/contacts_vcf/_load.html.erb
Normal file
@ -0,0 +1,16 @@
|
||||
<%- if Gem.available?('vpim') -%>
|
||||
<h3><%= l(:label_import) %></h3>
|
||||
|
||||
<span id='import_link'>
|
||||
<%= link_to l(:label_vcf_import), {}, :onclick => "Element.toggle('import_link'); Element.toggle('import_file'); return false;" %>
|
||||
</span>
|
||||
<% form_tag({:controller => :contacts_vcf, :action => :load, :project_id => @project}, :multipart => true ) do %>
|
||||
<span id='import_file' style="display:none;">
|
||||
<%= file_field_tag 'contact_vcf', :size => 30, :id => nil, :onchange => "this.form.submit()" -%>
|
||||
<small>
|
||||
<%= link_to 'Cancel', {}, :onclick => "Element.toggle('import_link'); Element.toggle('import_file'); return false;" %>
|
||||
</small>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<%- end -%>
|
||||
5
app/views/deal_categories/_form.html.erb
Normal file
5
app/views/deal_categories/_form.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<%= error_messages_for 'category' %>
|
||||
|
||||
<div class="box tabular">
|
||||
<p><%= f.text_field :name, :size => 30, :required => true %></p>
|
||||
</div>
|
||||
15
app/views/deal_categories/destroy.html.erb
Normal file
15
app/views/deal_categories/destroy.html.erb
Normal file
@ -0,0 +1,15 @@
|
||||
<h2><%=l(:label_deal_category)%>: <%=h @category.name %></h2>
|
||||
|
||||
<% form_tag({}) do %>
|
||||
<div class="box">
|
||||
<p><strong><%= l(:text_deal_category_destroy_question, @deal_count) %></strong></p>
|
||||
<p><label><%= radio_button_tag 'todo', 'nullify', true %> <%= l(:text_deal_category_destroy_assignments) %></label><br />
|
||||
<% if @categories.size > 0 %>
|
||||
<label><%= radio_button_tag 'todo', 'reassign', false %> <%= l(:text_deal_category_reassign_to) %></label>:
|
||||
<%= select_tag 'reassign_to_id', options_from_collection_for_select(@categories, 'id', 'name') %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= submit_tag l(:button_apply) %>
|
||||
<%= link_to l(:button_cancel), :controller => 'projects', :action => 'settings', :id => @project, :tab => 'contacts' %>
|
||||
<% end %>
|
||||
6
app/views/deal_categories/edit.html.erb
Normal file
6
app/views/deal_categories/edit.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h2><%=l(:label_deal_category)%></h2>
|
||||
|
||||
<% labelled_form_for :category, @category, :url => { :action => 'edit', :id => @category } do |f| %>
|
||||
<%= render :partial => 'deal_categories/form', :locals => { :f => f } %>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<% end %>
|
||||
1
app/views/deal_categories/index.html.erb
Normal file
1
app/views/deal_categories/index.html.erb
Normal file
@ -0,0 +1 @@
|
||||
<h2>DealCategories#index</h2>
|
||||
6
app/views/deal_categories/new.html.erb
Normal file
6
app/views/deal_categories/new.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h2><%=l(:label_issue_category_new)%></h2>
|
||||
|
||||
<% labelled_form_for :category, @category, :url => { :action => 'new', :project_id => @project } do |f| %>
|
||||
<%= render :partial => 'deal_categories/form', :locals => { :f => f } %>
|
||||
<%= submit_tag l(:button_create) %>
|
||||
<% end %>
|
||||
36
app/views/deal_contacts/_contacts.html.erb
Normal file
36
app/views/deal_contacts/_contacts.html.erb
Normal file
@ -0,0 +1,36 @@
|
||||
<% if @deal.all_contacts.any? %>
|
||||
<div id="deal_contacts">
|
||||
<div class="contextual">
|
||||
<%= link_to_remote l(:button_add),
|
||||
:url => {:controller => 'deal_contacts',
|
||||
:action => 'add',
|
||||
:project_id => @project,
|
||||
:deal_id => @deal} if User.current.allowed_to?({:controller => 'deal_contacts', :action => 'add'}, @project) %>
|
||||
|
||||
</div>
|
||||
|
||||
<h3><%= l(:label_contractor_plural) %></h3>
|
||||
|
||||
<% unless !(@show_form == "true") %>
|
||||
<% form_remote_tag(
|
||||
:url => {:controller => 'deal_contacts',
|
||||
:action => 'add',
|
||||
:deal_id => @deal,
|
||||
:project_id => @project},
|
||||
:method => :post,
|
||||
:html => {:id => 'add-contact-form'}) do |f| %>
|
||||
<p>
|
||||
<%= select_tag :contact_id, options_for_select(@project.contacts.sort!{|x, y| x.name <=> y.name }.collect {|m| [m.name, m.id]}), :prompt => "--- #{l(:actionview_instancetag_blank_option)} ---" %>
|
||||
<br>
|
||||
<%= submit_tag l(:button_add) %>
|
||||
<%= toggle_link l(:button_cancel), 'add-contact-form'%>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render :partial => 'common/contact_data', :object => @deal.contact %>
|
||||
<% @deal.related_contacts.each do |contact| %>
|
||||
<%= render :partial => 'common/contact_data', :object => contact, :locals => {:actions => remove_contractor_link(contact)} %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
35
app/views/deal_statuses/_form.html.erb
Normal file
35
app/views/deal_statuses/_form.html.erb
Normal file
@ -0,0 +1,35 @@
|
||||
<%= error_messages_for 'deal_status' %>
|
||||
|
||||
<div class="box tabular">
|
||||
<!--[form:deal_status]-->
|
||||
<p><label for="deal_status_name"><%=l(:field_name)%><span class="required"> *</span></label>
|
||||
<%= text_field 'deal_status', 'name' %></p>
|
||||
|
||||
|
||||
<p><label for="tag_color"><%=l(:field_color)%></label>
|
||||
<%= text_field 'deal_status', 'color_name', :class => "colorpicker" %>
|
||||
<!--[eoform:optvalue]-->
|
||||
</p>
|
||||
|
||||
<script>
|
||||
// var picker = new ColourPicker("picker", "trigger");
|
||||
$$('.colorpicker').each(function(el){var p = new ColorPicker(el)});
|
||||
</script>
|
||||
|
||||
<p><label for="deal_status_is_closed"><%=l(:field_deal_status_is_closed)%></label>
|
||||
<%= check_box 'deal_status', 'is_closed' %></p>
|
||||
|
||||
<p><label for="deal_status_is_default"><%=l(:field_is_default)%></label>
|
||||
<%= check_box 'deal_status', 'is_default' %></p>
|
||||
|
||||
<%= call_hook(:view_deal_statuses_form, :deal_status => @deal_status) %>
|
||||
|
||||
<!--[eoform:deal_status]-->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
6
app/views/deal_statuses/edit.html.erb
Normal file
6
app/views/deal_statuses/edit.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h2><%= link_to l(:label_deal_status_plural), :controller => 'deal_statuses', :action => 'index' %> » <%=h @deal_status %></h2>
|
||||
|
||||
<% form_tag({:action => 'update', :id => @deal_status}, :class => "tabular") do %>
|
||||
<%= render :partial => 'form' %>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<% end %>
|
||||
35
app/views/deal_statuses/index.html.erb
Normal file
35
app/views/deal_statuses/index.html.erb
Normal file
@ -0,0 +1,35 @@
|
||||
<div class="contextual">
|
||||
<%= link_to l(:label_deal_status_new), {:action => 'new'}, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<h2><%=l(:label_deal_status_plural)%></h2>
|
||||
|
||||
<table class="list">
|
||||
<thead><tr>
|
||||
<th><%=l(:field_status)%></th>
|
||||
<th><%=l(:field_is_default)%></th>
|
||||
<th><%=l(:field_is_closed)%></th>
|
||||
<th><%=l(:button_sort)%></th>
|
||||
<th></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<% for status in @deal_statuses %>
|
||||
<tr class="<%= cycle("odd", "even") %>">
|
||||
<td><%= link_to status.name, :action => 'edit', :id => status %></td>
|
||||
<td align="center"><%= checked_image status.is_default? %></td>
|
||||
<td align="center"><%= checked_image status.is_closed? %></td>
|
||||
<td align="center" style="width:15%;"><%= reorder_links('deal_status', {:action => 'update', :id => status}) %></td>
|
||||
<td class="buttons">
|
||||
<%= link_to(l(:button_delete), { :action => 'destroy', :id => status },
|
||||
:method => :post,
|
||||
:confirm => l(:text_are_you_sure),
|
||||
:class => 'icon icon-del') %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p class="pagination"><%= pagination_links_full @deal_status_pages %></p>
|
||||
|
||||
<% html_title(l(:label_deal_status_plural)) -%>
|
||||
6
app/views/deal_statuses/new.html.erb
Normal file
6
app/views/deal_statuses/new.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h2><%= link_to l(:label_deal_status_plural), :controller => 'deal_statuses', :action => 'index' %> » <%=l(:label_deal_status_new)%></h2>
|
||||
|
||||
<% form_tag({:action => 'create'}, :class => "tabular") do %>
|
||||
<%= render :partial => 'form' %>
|
||||
<%= submit_tag l(:button_create) %>
|
||||
<% end %>
|
||||
14
app/views/deals/_attributes.html.erb
Normal file
14
app/views/deals/_attributes.html.erb
Normal file
@ -0,0 +1,14 @@
|
||||
<% if @deal.custom_values.any? %>
|
||||
<div id="attributes">
|
||||
|
||||
<h3><%= l(:label_deal) %></h3>
|
||||
<table class="attributes">
|
||||
<% @deal.custom_values.each do |custom_value| %>
|
||||
<% if !custom_value.value.blank? %>
|
||||
<tr> <th class = "custom_field"><%= custom_value.custom_field.name %>:</th><td> <%=h show_value(custom_value) %></td> </tr>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
5
app/views/deals/_custom_field_filter.html.erb
Normal file
5
app/views/deals/_custom_field_filter.html.erb
Normal file
@ -0,0 +1,5 @@
|
||||
<span class="filter-condition">
|
||||
available_custom_fields
|
||||
<%= label_tag l(:label_assigned_to) + " " %>
|
||||
<%= select_tag :assigned_to_id, options_for_select(Deal.available_users(@project).collect{|u| [u.name, u.id.to_s]}.insert(0, [""]), params[:assigned_to_id]) %>
|
||||
</span>
|
||||
3
app/views/deals/_custom_field_form.html.erb
Normal file
3
app/views/deals/_custom_field_form.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<p><%= form.check_box :is_for_all %></p>
|
||||
<p><%= form.check_box :is_filter %></p>
|
||||
<p><%= form.check_box :visible, :label => l(:label_contacts_show_in_list) %></p>
|
||||
26
app/views/deals/_deals_statistics.html.erb
Normal file
26
app/views/deals/_deals_statistics.html.erb
Normal file
@ -0,0 +1,26 @@
|
||||
<% if deal_statuses.any? %>
|
||||
<div id="deals_statistics">
|
||||
<% if !(@project && !authorize_for(:sale_funel, :index)) %>
|
||||
<div class="contextual">
|
||||
<%= link_to l(:label_sale_funel), {:controller => 'sale_funel', :action => 'index', :project_id => @project} %>
|
||||
</div>
|
||||
<% end %>
|
||||
<h3><%= l(:label_statistics) %></h3>
|
||||
<table class="deals_statistics">
|
||||
<% deal_statuses.each do |deal_status| %>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="deal-status" style=<%= "background-color:#{deal_status.color_name};color:white;" %> >
|
||||
<%= h "#{deal_status.name}(#{@project ? @project.deals.count(:conditions => {:status_id => deal_status.id}) : Deal.count(:conditions => {:status_id => deal_status.id})})" %>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<strong>
|
||||
<%= @project ? deals_sum_to_currency(@project.deals.sum(:price, :conditions => {:status_id => deal_status.id}, :group => :currency)) : deals_sum_to_currency(Deal.sum(:price, :conditions => {:status_id => deal_status.id}, :group => :currency)) %>
|
||||
</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
38
app/views/deals/_form.html.erb
Normal file
38
app/views/deals/_form.html.erb
Normal file
@ -0,0 +1,38 @@
|
||||
<%= error_messages_for 'deal' %>
|
||||
<div class = "box tabular">
|
||||
<p><%= f.text_field :name, :label=>l(:field_deal_name), :size => 80, :required => true %></p>
|
||||
|
||||
<p class = "notes"><%= f.text_area :background , :cols => 80, :rows => 8, :class => 'wiki-edit', :label=>l(:field_deal_background) %></p> <%= wikitoolbar_for 'deal_background' %>
|
||||
|
||||
<% if @project.deal_statuses.any? %>
|
||||
<p><%= f.select :status_id, collection_for_status_select, :include_blank => false, :selected => @deal.status_id.to_s, :label=>l(:field_contact_status) %></p>
|
||||
<% end %>
|
||||
<% unless @project.deal_categories.empty? %>
|
||||
<p><%= f.select :category_id, (@project.deal_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
|
||||
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
|
||||
l(:label_deal_category_new),
|
||||
'category[name]',
|
||||
{:controller => 'deal_categories', :action => 'new', :project_id => @project},
|
||||
:title => l(:label_deal_category_new),
|
||||
:tabindex => 199) if authorize_for('deal_categories', 'new') %></p>
|
||||
<% end %>
|
||||
<p>
|
||||
<%= f.select :contact_id, (@project.contacts.visible.collect {|p| [ p.name, p.id ] }), :include_blank => true, :label=>l(:field_deal_contact) %>
|
||||
<%= link_to image_tag('add.png', :style => 'vertical-align: middle;'),
|
||||
{:controller => 'contacts', :action => 'new', :project_id => @project},
|
||||
:confirm => l(:text_are_you_sure), :title => l(:label_contact_new) if authorize_for('contacts', 'new') %>
|
||||
</p>
|
||||
<p>
|
||||
<%= f.text_field :price, :label => l(:field_deal_price), :size => 10 %>
|
||||
<%= select_tag "deal[currency]", options_for_select(collection_for_currencies_select.insert(0, ['', '']), @deal.currency), :include_blank => true %>
|
||||
|
||||
</p>
|
||||
|
||||
<% @deal.custom_field_values.each do |value| %>
|
||||
<p>
|
||||
<%= custom_field_tag_with_label :deal, value %>
|
||||
</p>
|
||||
<% end -%>
|
||||
|
||||
<p><%= f.select :assigned_to_id, (@project.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true, :label => l(:label_assigned_to) %></p>
|
||||
</div>
|
||||
54
app/views/deals/_list.html.erb
Normal file
54
app/views/deals/_list.html.erb
Normal file
@ -0,0 +1,54 @@
|
||||
<% form_tag({}) do -%>
|
||||
<%= hidden_field_tag 'back_url', url_for(params) %>
|
||||
<%= hidden_field_tag 'project_id', @project.id if @project %>
|
||||
<% unless @deals.empty? %>
|
||||
<table class="contacts index">
|
||||
<tbody>
|
||||
<% @deals.each do |deal| %>
|
||||
<tr class="hascontextmenu">
|
||||
<td class="checkbox">
|
||||
<%= check_box_tag "ids[]", deal.id, false, :onclick => "toggleContact(event, this);" %>
|
||||
</td>
|
||||
<td class="avatar">
|
||||
<%= link_to avatar_to(deal, :size => "32"), {:controller => 'deals', :action => 'show', :id => deal.id}, :id => "avatar" %>
|
||||
</td>
|
||||
<td class="name">
|
||||
<h1 class="deal_name"><%= link_to deal.name, :controller => 'deals', :action => 'show', :id => deal.id %></h1>
|
||||
<h2>
|
||||
<%= link_to_source(deal.contact) if deal.contact %>
|
||||
|
||||
</h2>
|
||||
</td>
|
||||
|
||||
<td class="info">
|
||||
<div class="deal-sum"><strong><%= deal_price(deal) %></strong>
|
||||
<% if deal.status && deal.project.deal_statuses.any? %>
|
||||
<span class="deal-status tags" style = <%= "background-color:#{deal.status.color_name};color:white;" %> >
|
||||
<%= h deal.status %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="description" >
|
||||
<%= h deal.category %>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr class="total">
|
||||
<th/>
|
||||
<th/>
|
||||
<th class="title"> <%= "#{l(:label_total)} (#{@deals_count}):" %> </th>
|
||||
<th class="sum">
|
||||
<%= deals_sum_to_currency(@deals_sum).gsub(' / ', '<br/>') %>
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<%= contacts_paginator @deals_pages, :params => {:project_id => params[:project_id]} if @deals_pages %>
|
||||
|
||||
<% else %>
|
||||
<p class="nodata"><%=l(:label_no_data)%></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= context_menu url_for( {:controller => "deals", :action => "context_menu"} )%>
|
||||
28
app/views/deals/_related_deals.html.erb
Normal file
28
app/views/deals/_related_deals.html.erb
Normal file
@ -0,0 +1,28 @@
|
||||
<div id="deals">
|
||||
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized l(:label_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project, :contact_id => @contact} %>
|
||||
</div>
|
||||
|
||||
<h3><%= "#{l(:label_deal_plural)}" %> <%= " - #{h number_to_currency(related_deals.sum{|d| d.price || 0})}" if false %></h3>
|
||||
|
||||
<% if related_deals.any? %>
|
||||
<table class="related_deals">
|
||||
<% related_deals.each do |deal| %>
|
||||
<tr>
|
||||
<td class="name" style="vertical-align: top;">
|
||||
<h4>
|
||||
<%= link_to deal.name, {:controller => 'deals', :action => 'show', :id => deal.id } %>
|
||||
</h4>
|
||||
<%= deal_price(deal) %>
|
||||
<% if deal.status %>
|
||||
<span class="deal-status" style = <%= "background-color:#{deal.status.color_name};color:white;" %> >
|
||||
<%= h deal.status %>
|
||||
</span>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<% end %>
|
||||
</div>
|
||||
80
app/views/deals/bulk_edit.html.erb
Normal file
80
app/views/deals/bulk_edit.html.erb
Normal file
@ -0,0 +1,80 @@
|
||||
<h2><%= l(:label_bulk_edit_selected_deals) %></h2>
|
||||
|
||||
|
||||
<div class="box" id="duplicates">
|
||||
<ul>
|
||||
<% @deals.each do |deal| %>
|
||||
<li>
|
||||
<%= avatar_to deal, :size => "16" %>
|
||||
<%= link_to deal.full_name, polymorphic_url(deal) %>
|
||||
<%= "(#{deal_price(deal)}) " unless deal.price.blank? %>
|
||||
<% if deal.status %>
|
||||
<span class="deal-status" style = <%= "background-color:#{deal.status.color_name};color:white;" %> >
|
||||
<%= h deal.status %>
|
||||
</span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<% form_tag(:action => 'bulk_update') do %>
|
||||
<%= @deals.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
|
||||
<div class="box tabular">
|
||||
<fieldset class="attributes">
|
||||
<legend><%= l(:label_change_properties) %></legend>
|
||||
|
||||
<% if @available_statuses.any? %>
|
||||
<p>
|
||||
<label><%= l(:field_status) %></label>
|
||||
<%= select_tag('deal[status_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
options_from_collection_for_select(@available_statuses, :id, :name)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
||||
<% if @available_categories.any? %>
|
||||
<p>
|
||||
<label><%= l(:field_category) %></label>
|
||||
<%= select_tag('deal[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_none), :value => 'none') +
|
||||
options_from_collection_for_select(@available_categories, :id, :name)) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<p>
|
||||
<label><%= l(:label_assigned_to) %></label>
|
||||
<%= select_tag('deal[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
|
||||
content_tag('option', l(:label_nobody), :value => 'none') +
|
||||
options_from_collection_for_select(@assignables, :id, :name)) %>
|
||||
</p>
|
||||
<p>
|
||||
<label><%= l(:field_deal_currency) %></label>
|
||||
<%= select_tag "deal[currency]", options_for_select(collection_for_currencies_select.insert(0, ['', '']), '') %>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<% @deals.first.custom_field_values.each do |value| %>
|
||||
<p>
|
||||
<% value.value = '' %>
|
||||
<%= custom_field_tag_with_label :contact, value %>
|
||||
</p>
|
||||
<% end -%>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset><legend><%= l(:field_notes) %></legend>
|
||||
<%= text_area_tag 'note[content]', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %>
|
||||
<%= wikitoolbar_for 'note_content' %>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<p><%= submit_tag l(:button_submit) %></p>
|
||||
<% end %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<% end %>
|
||||
46
app/views/deals/context_menu.html.erb
Normal file
46
app/views/deals/context_menu.html.erb
Normal file
@ -0,0 +1,46 @@
|
||||
<ul>
|
||||
<% if !@deal.nil? %>
|
||||
<li><%= context_menu_link l(:button_edit), {:controller => 'deals', :action => 'edit', :id => @deal},
|
||||
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
|
||||
|
||||
|
||||
<% if User.current.logged? %>
|
||||
<li><%= watcher_link(@deal, User.current) %></li>
|
||||
<% end %>
|
||||
|
||||
<% else %>
|
||||
<li><%= context_menu_link l(:button_edit), {:controller => 'deals', :action => 'bulk_edit', :ids => @deals.collect(&:id)},
|
||||
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
|
||||
<% end %>
|
||||
|
||||
<% unless @project.nil? || @project.deal_categories.empty? -%>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_category) %></a>
|
||||
<ul>
|
||||
<% @project.deal_categories.each do |u| -%>
|
||||
<li><%= context_menu_link u.name, {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'category_id' => u}, :back_url => @back}, :method => :post,
|
||||
:selected => (@deal && u == @deal.category), :disabled => !@can[:edit] %></li>
|
||||
<% end -%>
|
||||
<li><%= context_menu_link l(:label_none), {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
|
||||
:selected => (@deal && @deal.category.nil?), :disabled => !@can[:edit] %></li>
|
||||
</ul>
|
||||
</li>
|
||||
<% end -%>
|
||||
|
||||
<% unless @project.nil? || @project.deal_statuses.empty? -%>
|
||||
<li class="folder">
|
||||
<a href="#" class="submenu"><%= l(:field_contact_status) %></a>
|
||||
<ul>
|
||||
<% @project.deal_statuses.each do |s| -%>
|
||||
<li><%= context_menu_link s.name, {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'status_id' => s}, :back_url => @back}, :method => :post,
|
||||
:selected => (@deal && s == @deal.status), :disabled => !@can[:edit] %></li>
|
||||
<% end -%>
|
||||
</ul>
|
||||
</li>
|
||||
<% end -%>
|
||||
|
||||
|
||||
<li><%= context_menu_link l(:button_delete), {:controller => 'deals', :action => 'bulk_destroy', :ids => @deals.collect(&:id), :project_id => @project},
|
||||
:method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
|
||||
</ul>
|
||||
|
||||
6
app/views/deals/edit.html.erb
Normal file
6
app/views/deals/edit.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h2><%= l(:label_deal_edit_information) %></h2>
|
||||
|
||||
<% labelled_form_for :deal, @deal, :url => {:action => 'update', :id => @deal} do |f| %>
|
||||
<%= render :partial => 'form', :locals => {:f => f} %>
|
||||
<%= submit_tag l(:button_save) -%>
|
||||
<% end -%>
|
||||
100
app/views/deals/index.html.erb
Normal file
100
app/views/deals/index.html.erb
Normal file
@ -0,0 +1,100 @@
|
||||
<% content_for(:header_tags) do %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<meta name = "format-detection" content = "telephone=no">
|
||||
<% end %>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
<%= render :partial => 'deals_statistics' %>
|
||||
<%= render :partial => 'notes/last_notes', :object => @last_notes %>
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<div class="contextual">
|
||||
<%= link_to_if_authorized l(:label_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %>
|
||||
</div>
|
||||
|
||||
<div class="filters">
|
||||
<% form_tag(params, :id => "query_form") do %>
|
||||
<%= hidden_field_tag('project_id', @project.to_param) if @project %>
|
||||
<% no_filters = ((params[:status_id].blank? || params[:status_id] == 'o') && params[:period].blank? && params[:assigned_to_id].blank? && params[:category_id].blank?) %>
|
||||
|
||||
<h2 class="contacts_header">
|
||||
<span id='scope_header' class="scope_title">
|
||||
<%= l(:label_deal_plural) %>
|
||||
</span>
|
||||
|
||||
<span class="live_search">
|
||||
<%= label_tag :search, l(:label_search), :id => "search_overlabel" %>
|
||||
<%= text_field_tag(:search, params[:search], :autocomplete => "off", :size => "35", :class => "live_search_field", :onfocus => "Element.hide('search_overlabel'); return false;", :onblur => "if (this.value == '') {Element.show('search_overlabel');}" ) %>
|
||||
<!-- , :onfocus => "if (this.value == 'Поиск') {this.value = '';}", :onblur => "if (this.value == '') {this.value ='Поиск'; this.style.color='#aaa'}" -->
|
||||
<%= observe_field("search",
|
||||
:frequency => 2,
|
||||
:update => 'contact_list',
|
||||
:url => {:controller => 'deals', :action => 'index', :project_id => @project },
|
||||
:with => "Form.serialize('query_form')") %>
|
||||
</span>
|
||||
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
<fieldset id="filters" class="collapsible <%= 'collapsed' if no_filters %>">
|
||||
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
|
||||
<div style="<%= 'display: none;' if no_filters %>">
|
||||
<p>
|
||||
<% if !deal_statuses.empty? %>
|
||||
<span class="filter-condition">
|
||||
<%= label_tag l(:label_deal_status) + " " %>
|
||||
<%= select_tag :status_id, options_for_select(collection_for_status_select.insert(0, [l(:label_open_issues), "o"]).insert(0, [l(:label_all), ""]), params[:status_id]) %>
|
||||
</span>
|
||||
<% end %>
|
||||
<span class="filter-condition">
|
||||
<%= label_tag l(:label_created_on) + " "%>
|
||||
<%= select_tag 'period', options_for_period_select(params[:period]) %>
|
||||
</span>
|
||||
<% if @project && !@project.deal_categories.empty? %>
|
||||
<span class="filter-condition">
|
||||
<%= label_tag l(:label_deal_category) + " "%>
|
||||
<%= select_tag 'category_id', options_for_select(@project.deal_categories.collect {|c| [c.name, c.id.to_s]}.insert(0, [""]), params[:category_id]) %>
|
||||
</span>
|
||||
<% end %>
|
||||
<span class="filter-condition">
|
||||
<%= label_tag l(:label_assigned_to) + " " %>
|
||||
<%= select_tag :assigned_to_id, options_for_select(Deal.available_users(@project).collect{|u| [u.name, u.id.to_s]}.insert(0, [""]), params[:assigned_to_id]) %>
|
||||
</span>
|
||||
|
||||
<!-- <%= render :partial => 'custom_field_filter' %> -->
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
<p class="buttons hide-when-print">
|
||||
|
||||
<%= link_to_remote l(:button_apply),
|
||||
{ :url => {},
|
||||
:update => "contact_list",
|
||||
:with => "Form.serialize('query_form')"
|
||||
}, :class => 'icon icon-checked' %>
|
||||
|
||||
<%= link_to l(:button_clear),
|
||||
{:project_id => @project, :set_filter => 1 },
|
||||
:method => :get,
|
||||
:update => "contact_list",
|
||||
:class => 'icon icon-reload' %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div id="contact_list">
|
||||
<%= render :partial => 'list' %>
|
||||
</div>
|
||||
|
||||
<% html_title l(:label_deal_plural) %>
|
||||
|
||||
6
app/views/deals/new.html.erb
Normal file
6
app/views/deals/new.html.erb
Normal file
@ -0,0 +1,6 @@
|
||||
<h2><%= l(:label_deal_new) %></h2>
|
||||
|
||||
<% labelled_form_for :deal, @deal, :url => {:action => 'create', :project_id => @project} do |f| %>
|
||||
<%= render :partial => 'form', :locals => {:f => f} %>
|
||||
<%= submit_tag l(:button_save) -%>
|
||||
<% end -%>
|
||||
94
app/views/deals/show.html.erb
Normal file
94
app/views/deals/show.html.erb
Normal file
@ -0,0 +1,94 @@
|
||||
<div class="contextual">
|
||||
<% replace_watcher ||= 'watcher' %>
|
||||
<%= watcher_tag(@deal, User.current, {:id => replace_watcher, :replace => ['watcher','watcher2']}) %>
|
||||
<%= link_to_if_authorized l(:button_edit), {:controller => 'deals', :action => 'edit', :id => @deal}, :class => 'icon icon-edit' unless @deal.nil? %>
|
||||
<%= link_to_if_authorized l(:button_delete), {:controller => 'deals', :action => 'destroy', :id => @deal}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' unless @deal.nil? %>
|
||||
</div>
|
||||
<h2><%= "#{l(:label_deal)} ##{@deal.id}" %></h2>
|
||||
<div class="deal details">
|
||||
|
||||
<table class="subject_header">
|
||||
<tr>
|
||||
<td class="avatar"><%= avatar_to(@deal, :size => "64") %></td>
|
||||
<td class="name" style="vertical-align: top;">
|
||||
<h1><%= h @deal.contact.name + ": " if @deal.contact %> <%= @deal.name %></h1>
|
||||
<p><%= h @deal.category %></p>
|
||||
<% if @deal.status && @project.deal_statuses.any? %>
|
||||
<div id="deal-status">
|
||||
<span class="deal-status" style = <%= "background-color:#{@deal.status.color_name};color:white;" %> >
|
||||
<%= h @deal.status %>
|
||||
</span>
|
||||
<% if authorize_for('deals', 'edit') %>
|
||||
<span class="contextual">
|
||||
<%= link_to l(:label_deal_change_status), {}, :onclick => "Element.show('edit_status_form'); Element.hide('deal-status'); return false;", :id => 'edit_status_link' %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% form_tag( {:controller => 'deals',
|
||||
:action => 'update',
|
||||
:project_id => @project,
|
||||
:id => @deal },
|
||||
:multipart => true,
|
||||
:id => "edit_status_form",
|
||||
:style => "display:none; size: 100%" ) do %>
|
||||
<%= select :deal, :status_id, options_for_select(collection_for_status_select, @deal.status_id.to_s), { :include_blank => false } %>
|
||||
<%= submit_tag l(:button_save) %>
|
||||
<%= link_to l(:button_cancel), {}, :onclick => "Element.hide('edit_status_form'); Element.show('deal-status'); return false;" %>
|
||||
<br>
|
||||
|
||||
<% end %>
|
||||
<% end %>
|
||||
</td>
|
||||
<% if !@deal.price.blank? %>
|
||||
<td class="subject_info">
|
||||
<ul>
|
||||
<li class="price icon icon-money-dollar"><%= deal_price(@deal) %></li>
|
||||
</ul>
|
||||
</td>
|
||||
<% end %>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
<% if authorize_for('notes', 'add_note') %>
|
||||
<hr />
|
||||
<%= render :partial => 'notes/add', :locals => {:note_source => @deal} %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="comments">
|
||||
<h3><%= l(:label_note_plural) %></h3>
|
||||
<div id="notes">
|
||||
<%= render :partial => 'notes/note_item', :collection => @deal.notes, :locals => {:note_source => @deal} %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<% content_for :sidebar do %>
|
||||
<%= render :partial => 'common/sidebar' %>
|
||||
|
||||
<%= render :partial => 'attributes' %>
|
||||
|
||||
<%= render :partial => 'common/responsible_user', :object => @deal %>
|
||||
<%= render :partial => 'deal_contacts/contacts' %>
|
||||
<%= render :partial => 'common/notes_attachments', :object => @deal_attachments %>
|
||||
|
||||
<% if !@deal.background.blank? %>
|
||||
<h3><%= l(:label_contact_background_info) %></h3>
|
||||
<div class="wiki"><%= textilizable(@deal, :background) %></div>
|
||||
<% end %>
|
||||
|
||||
<%= render :partial => 'common/recently_viewed' %>
|
||||
|
||||
<% end %>
|
||||
|
||||
<% html_title "#{l(:label_deal)} ##{@deal.id}: #{@deal.name}" %>
|
||||
|
||||
<% content_for :header_tags do %>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %>
|
||||
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
|
||||
<meta name = "format-detection" content = "telephone=no">
|
||||
<% end %>
|
||||
72
app/views/issues/_contacts.html.erb
Normal file
72
app/views/issues/_contacts.html.erb
Normal file
@ -0,0 +1,72 @@
|
||||
<% if !@issue.blank? && (User.current.allowed_to?(:view_contacts, @project) || User.current.admin?) %>
|
||||
|
||||
<div id="issue_contacts">
|
||||
<style type="text/css">
|
||||
#issue_contacts ul {margin: 0; padding: 0;}
|
||||
#issue_contacts li {list-style-type:none; margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
|
||||
#issue_contacts select {width: 95%; display: block;}
|
||||
#issue_contacts a.delete {opacity: 0.4;}
|
||||
#issue_contacts a.delete:hover {opacity: 1;}
|
||||
#issue_contacts img.gravatar {vertical-align: middle; margin: 0 4px 2px 0;}
|
||||
</style>
|
||||
|
||||
<div class="contextual">
|
||||
<%= link_to_remote l(:button_add),
|
||||
:url => {:controller => 'contacts_tasks',
|
||||
:action => 'add',
|
||||
:project_id => @project,
|
||||
:issue_id => @issue} if User.current.allowed_to?({:controller => 'contacts_tasks',
|
||||
:action => 'add'}, @project) %>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h3><%= l(:label_contact_plural) %> </h3>
|
||||
|
||||
<% unless !(@show_form == "true") %>
|
||||
<% form_remote_tag(
|
||||
:url => {:controller => 'contacts_tasks',
|
||||
:action => 'add',
|
||||
:issue_id => @issue,
|
||||
:project_id => @project},
|
||||
:method => :post,
|
||||
:html => {:id => 'add-contact-form'}) do |f| %>
|
||||
<p><%= select_tag :contact_id, options_for_select(@project.contacts.sort!{|x, y| x.name <=> y.name }.collect {|m| [m.name, m.id]}), :prompt => "--- #{l(:actionview_instancetag_blank_option)} ---" %>
|
||||
|
||||
<%= submit_tag l(:button_add) %>
|
||||
<%= toggle_link l(:button_cancel), 'add-contact-form'%></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
|
||||
<ul>
|
||||
<% @issue.contacts.each do |contact| %>
|
||||
<li>
|
||||
<%= avatar_to contact, :size => "16" %>
|
||||
<%= link_to_source contact %>
|
||||
<%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
|
||||
<% if User.current.allowed_to?(:delete_contacts, @project) %>
|
||||
<%= link_to_remote(image_tag('delete.png'),
|
||||
:url => { :controller => 'contacts_tasks',
|
||||
:action => 'delete',
|
||||
:issue_id => @issue,
|
||||
:project_id => @project,
|
||||
:contact_id => contact.id},
|
||||
:method => :delete,
|
||||
:confirm => l(:text_are_you_sure),
|
||||
:html => {:class => "delete",
|
||||
:title => l(:button_delete) }) %>
|
||||
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<% end %>
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user