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:
David Arranz 2012-01-03 16:18:32 +00:00
parent 766e08a527
commit 946162f5d0
245 changed files with 12756 additions and 0 deletions

33
README.rdoc Normal file
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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('&#171; ' + 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) + ' &#187;'), 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 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
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

View File

@ -0,0 +1,2 @@
module ContactsSettingsHelper
end

View 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 << "&nbsp;" }
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 }'>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
#{c}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</option>" }.join("\n")
html << "</select>"
end
end
end

View File

@ -0,0 +1,2 @@
module DealCategoriesHelper
end

View File

@ -0,0 +1,2 @@
module DealStatusesHelper
end

104
app/helpers/deals_helper.rb Normal file
View 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

View 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
View 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

View File

@ -0,0 +1,7 @@
class ContactCustomField < CustomField
unloadable
def type_name
:label_contact_plural
end
end

View 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

View 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

View 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

View 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
View 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

View 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

View 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
View 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

View 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
View 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
View 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

View File

@ -0,0 +1,7 @@
class NoteCustomField < CustomField
unloadable
def type_name
:label_note_plural
end
end

View 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
View 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

View 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>

View 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>

View 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 %>

View 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>

View 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 %>

View 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) %>

View 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>

View 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 %>

View 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>

View 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>

View 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"} )%>

View 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()") %>

View 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>

View 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>

View 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 %>

View 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 %>

View 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>

View 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 %>

View 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 %>

View 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

View 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 %>

View 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 %>

View 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

View 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 %>

View 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>

View 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 %>

View File

@ -0,0 +1 @@
<%= textilizable(@params[:message], :only_path => false).gsub(/&para;/, '') %>

View File

@ -0,0 +1 @@
<%= @params[:message] %>

View 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>

View 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 -%>

View File

@ -0,0 +1,32 @@
<h2><%= link_to l(:contacts_title), :controller => 'settings', :action => 'plugin', :id => 'contacts', :tab => 'tags' %> &#187; <%=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 %>

View File

@ -0,0 +1 @@
<h2>ContactsTags#index</h2>

View 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) %>

View 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>

View 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 %>

View 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 -%>

View File

@ -0,0 +1,5 @@
<%= error_messages_for 'category' %>
<div class="box tabular">
<p><%= f.text_field :name, :size => 30, :required => true %></p>
</div>

View 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 %>

View 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 %>

View File

@ -0,0 +1 @@
<h2>DealCategories#index</h2>

View 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 %>

View 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 %>

View 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 %>

View File

@ -0,0 +1,6 @@
<h2><%= link_to l(:label_deal_status_plural), :controller => 'deal_statuses', :action => 'index' %> &#187; <%=h @deal_status %></h2>
<% form_tag({:action => 'update', :id => @deal_status}, :class => "tabular") do %>
<%= render :partial => 'form' %>
<%= submit_tag l(:button_save) %>
<% end %>

View 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)) -%>

View File

@ -0,0 +1,6 @@
<h2><%= link_to l(:label_deal_status_plural), :controller => 'deal_statuses', :action => 'index' %> &#187; <%=l(:label_deal_status_new)%></h2>
<% form_tag({:action => 'create'}, :class => "tabular") do %>
<%= render :partial => 'form' %>
<%= submit_tag l(:button_create) %>
<% end %>

View 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 %>

View 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>

View 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>

View 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 %>

View 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>

View 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"} )%>

View 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>

View 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 %>

View 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>

View 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 -%>

View 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) %>

View 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 -%>

View 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 %>

View 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