diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..1ec49a8 --- /dev/null +++ b/README.rdoc @@ -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 + +
+rake db:migrate:plugin NAME=redmine_contacts VERSION=0 RAILS_ENV=production 
+rm -r vendor/plugins/redmine_contacts
+
+ + +== 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 \ No newline at end of file diff --git a/app/controllers/contacts_controller.rb b/app/controllers/contacts_controller.rb new file mode 100644 index 0000000..a7b08d4 --- /dev/null +++ b/app/controllers/contacts_controller.rb @@ -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 diff --git a/app/controllers/contacts_duplicates_controller.rb b/app/controllers/contacts_duplicates_controller.rb new file mode 100644 index 0000000..9d0276a --- /dev/null +++ b/app/controllers/contacts_duplicates_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/contacts_mailer_controller.rb b/app/controllers/contacts_mailer_controller.rb new file mode 100644 index 0000000..fb6a26a --- /dev/null +++ b/app/controllers/contacts_mailer_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/contacts_projects_controller.rb b/app/controllers/contacts_projects_controller.rb new file mode 100644 index 0000000..79e2c06 --- /dev/null +++ b/app/controllers/contacts_projects_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/contacts_settings_controller.rb b/app/controllers/contacts_settings_controller.rb new file mode 100644 index 0000000..732b59a --- /dev/null +++ b/app/controllers/contacts_settings_controller.rb @@ -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 diff --git a/app/controllers/contacts_tags_controller.rb b/app/controllers/contacts_tags_controller.rb new file mode 100644 index 0000000..17def8e --- /dev/null +++ b/app/controllers/contacts_tags_controller.rb @@ -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 diff --git a/app/controllers/contacts_tasks_controller.rb b/app/controllers/contacts_tasks_controller.rb new file mode 100644 index 0000000..92fd9f5 --- /dev/null +++ b/app/controllers/contacts_tasks_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/contacts_vcf_controller.rb b/app/controllers/contacts_vcf_controller.rb new file mode 100644 index 0000000..a9d53b3 --- /dev/null +++ b/app/controllers/contacts_vcf_controller.rb @@ -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 diff --git a/app/controllers/deal_categories_controller.rb b/app/controllers/deal_categories_controller.rb new file mode 100644 index 0000000..7ebb3e5 --- /dev/null +++ b/app/controllers/deal_categories_controller.rb @@ -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', '' + 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 diff --git a/app/controllers/deal_contacts_controller.rb b/app/controllers/deal_contacts_controller.rb new file mode 100644 index 0000000..70122b0 --- /dev/null +++ b/app/controllers/deal_contacts_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/deal_statuses_controller.rb b/app/controllers/deal_statuses_controller.rb new file mode 100644 index 0000000..8ce32c0 --- /dev/null +++ b/app/controllers/deal_statuses_controller.rb @@ -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 diff --git a/app/controllers/deals_controller.rb b/app/controllers/deals_controller.rb new file mode 100644 index 0000000..8aa33b0 --- /dev/null +++ b/app/controllers/deals_controller.rb @@ -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 diff --git a/app/controllers/deals_tasks_controller.rb b/app/controllers/deals_tasks_controller.rb new file mode 100644 index 0000000..67b878e --- /dev/null +++ b/app/controllers/deals_tasks_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb new file mode 100644 index 0000000..28289b0 --- /dev/null +++ b/app/controllers/notes_controller.rb @@ -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 diff --git a/app/controllers/sale_funel_controller.rb b/app/controllers/sale_funel_controller.rb new file mode 100644 index 0000000..9f2e6a1 --- /dev/null +++ b/app/controllers/sale_funel_controller.rb @@ -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 diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb new file mode 100644 index 0000000..c3368f5 --- /dev/null +++ b/app/controllers/tasks_controller.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/contacts_helper.rb b/app/helpers/contacts_helper.rb new file mode 100644 index 0000000..968af0f --- /dev/null +++ b/app/helpers/contacts_helper.rb @@ -0,0 +1,355 @@ +module ContactsHelper + + def authorized_for_permission?(permission, project, global = false) + User.current.allowed_to?(permission, project, :global => global) + end + + def contacts_filters_for_select(selected) + selected ||= "" + options_for_select([[contacts_filter_name('0'), ActiveRecord::Base.connection.quoted_false.gsub(/'/, '')], + [contacts_filter_name('1'), ActiveRecord::Base.connection.quoted_true.gsub(/'/, '')], + [contacts_filter_name(''), ''], + [contacts_filter_name('2'), '2'], + [contacts_filter_name('3'), '3'], + ['--------'], + [l(:button_cancel), '-1']], :selected => selected, :disabled => "--------") + end + + def contacts_filter_name(value) + case value + when '0' + l(:label_all_people) + when '1' + l(:label_all_companies) + when '2' + l(:label_recently_added_contacts) + when '3' + l(:label_created_by_me) + else + l(:label_all_people_and_companies) + end + end + + def skype_to(skype_name, name = nil) + return link_to skype_name, 'skype:' + skype_name + '?call' unless skype_name.blank? + end + + def link_to_remote_list_update(text, url_params) + link_to_remote(text, + {:url => url_params, :method => :get, :update => 'contact_list', :complete => 'window.scrollTo(0,0)'}, + {:href => url_for(:params => url_params)} + ) + end + + + def contacts_paginator(paginator, page_options) + page_param = page_options.delete(:page_param) || :page + per_page_links = page_options.delete(:per_page_links) + url_param = params.dup + # don't reuse query params if filters are present + url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter) + + html = '' + if paginator.current.previous + html << link_to_remote_list_update('« ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' ' + end + + html << (pagination_links_each(paginator, page_options) do |n| + link_to_remote_list_update(n.to_s, url_param.merge(page_param => n)) + end || '') + + if paginator.current.next + html << ' ' + link_to_remote_list_update((l(:label_next) + ' »'), url_param.merge(page_param => paginator.current.next)) + end + + html + + end + + def contact_url(contact) + return {:controller => 'contacts', :action => 'show', :project_id => @project, :id => contact.id } + end + + def deal_url(deal) + return {:controller => 'deals', :action => 'show', :id => deal.id } + end + + def note_source_url(note_source) + polymorphic_url(note_source) + # return {:controller => note_source.class.name.pluralize.downcase, :action => 'show', :project_id => @project, :id => note_source.id } + end + + def link_to_source(note_source, options={}) + return link_to note_source.name, note_source_url(note_source), options + end + + def avatar_to(obj, options = { }) + options[:size] = "64" unless options[:size] + size = options[:size] + options[:size] = options[:size] + "x" + options[:size] + options[:class] = "gravatar" + + avatar = obj.avatar unless Rails::env == "development" + + if avatar && FileTest.exists?(avatar.diskfile) && avatar.is_thumbnailable? then # and obj.visible? + avatar_url = url_for :only_path => false, :controller => 'attachments', :action => 'download', :id => avatar, :filename => avatar.filename + thumbnail_url = url_for(:only_path => false, + :controller => 'attachments', + :action => 'thumbnail', + :id => avatar, + :size => size) + + image_url = Object.const_defined?(:Magick) ? thumbnail_url : avatar_url + + if options[:full_size] then + image = link_to image_tag(image_url, options), avatar_url + else + image = image_tag(image_url, options) + end + end + + + plugins_images = case obj + when Deal then "deal.png" + when Contact then obj.is_company ? "company.png" : "person.png" + end + plugins_images = image_path(plugins_images, :plugin => :redmine_contacts) + + if !image && Setting.plugin_contacts[:use_gravatar] && obj.class == Contact + options[:default] = "#{request.protocol}#{request.host_with_port}" + plugins_images + options.merge!({:ssl => (defined?(request) && request.ssl?)}) + image = gravatar(obj.emails.first.downcase, options) rescue nil + end + + image ||= image_tag(plugins_images, options) + + end + + def link_to_add_phone(name) + fields = '

' + label_tag(l(:field_contact_phone)) + + text_field_tag( "contact[phones][]", '', :size => 30 ) + + link_to_function(l(:label_remove), "removeField(this)") + '

' + 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 << "" + 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" + # + #
<%= link_to_issue(issue) %> + # <%= render_issue_tooltip(issue) %> + #
+ # + 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| "#{mail_to email}"}.join(', ') : '' + phones = contact.phones.any? ? contact.phones.map{|phone| "#{phone}"}.join(', ') : '' + + s = link_to_contact(contact, options) + "

" + s << "#{@cached_label_job_title}: #{contact.job_title}
" unless contact.job_title.blank? + s << "#{@cached_label_company}: #{link_to(contact.contact_company.name, {:controller => 'contacts', :action => 'show', :id => contact.contact_company.id })}
" if !contact.contact_company.blank? && !contact.is_company + s << "#{@cached_label_email}: #{emails}
" if contact.emails.any? + s << "#{@cached_label_phone}: #{phones}
" 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 diff --git a/app/helpers/contacts_settings_helper.rb b/app/helpers/contacts_settings_helper.rb new file mode 100644 index 0000000..3f89e17 --- /dev/null +++ b/app/helpers/contacts_settings_helper.rb @@ -0,0 +1,2 @@ +module ContactsSettingsHelper +end diff --git a/app/helpers/contacts_tags_helper.rb b/app/helpers/contacts_tags_helper.rb new file mode 100644 index 0000000..c43d194 --- /dev/null +++ b/app/helpers/contacts_tags_helper.rb @@ -0,0 +1,29 @@ +module ContactsTagsHelper + + def color_picker(name, color="#aaa") + #build the hexes + hexes = [] + (0..15).step(3) do |one| + (0..15).step(3) do |two| + (0..15).step(3) do |three| + hexes << "#" + one.to_s(16) + two.to_s(16) + three.to_s(16) + end + end + end + arr = [] + on_change_function = "onChange=\"this.style.backgroundColor=this.options[this.selectedIndex].style.backgroundColor;\"" + 10.times { arr << " " } + returning html = '' do + html << "" + end + end + + end \ No newline at end of file diff --git a/app/helpers/deal_categories_helper.rb b/app/helpers/deal_categories_helper.rb new file mode 100644 index 0000000..a921ae6 --- /dev/null +++ b/app/helpers/deal_categories_helper.rb @@ -0,0 +1,2 @@ +module DealCategoriesHelper +end diff --git a/app/helpers/deal_statuses_helper.rb b/app/helpers/deal_statuses_helper.rb new file mode 100644 index 0000000..3164a58 --- /dev/null +++ b/app/helpers/deal_statuses_helper.rb @@ -0,0 +1,2 @@ +module DealStatusesHelper +end diff --git a/app/helpers/deals_helper.rb b/app/helpers/deals_helper.rb new file mode 100644 index 0000000..7c2a6e4 --- /dev/null +++ b/app/helpers/deals_helper.rb @@ -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 diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb new file mode 100644 index 0000000..8baa74e --- /dev/null +++ b/app/helpers/notes_helper.rb @@ -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 diff --git a/app/models/contact.rb b/app/models/contact.rb new file mode 100644 index 0000000..69edccb --- /dev/null +++ b/app/models/contact.rb @@ -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 diff --git a/app/models/contact_custom_field.rb b/app/models/contact_custom_field.rb new file mode 100644 index 0000000..ee1f21c --- /dev/null +++ b/app/models/contact_custom_field.rb @@ -0,0 +1,7 @@ +class ContactCustomField < CustomField + unloadable + + def type_name + :label_contact_plural + end +end \ No newline at end of file diff --git a/app/models/contact_note.rb b/app/models/contact_note.rb new file mode 100644 index 0000000..99bb3ed --- /dev/null +++ b/app/models/contact_note.rb @@ -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 \ No newline at end of file diff --git a/app/models/contacts_mailer.rb b/app/models/contacts_mailer.rb new file mode 100644 index 0000000..c6ecc11 --- /dev/null +++ b/app/models/contacts_mailer.rb @@ -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{^ 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 diff --git a/app/models/contacts_setting.rb b/app/models/contacts_setting.rb new file mode 100644 index 0000000..cfa3d53 --- /dev/null +++ b/app/models/contacts_setting.rb @@ -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 diff --git a/app/models/contacts_tasks.rb b/app/models/contacts_tasks.rb new file mode 100644 index 0000000..06ecaa0 --- /dev/null +++ b/app/models/contacts_tasks.rb @@ -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 diff --git a/app/models/deal.rb b/app/models/deal.rb new file mode 100644 index 0000000..4890eb4 --- /dev/null +++ b/app/models/deal.rb @@ -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 diff --git a/app/models/deal_category.rb b/app/models/deal_category.rb new file mode 100644 index 0000000..1e185b4 --- /dev/null +++ b/app/models/deal_category.rb @@ -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 diff --git a/app/models/deal_custom_field.rb b/app/models/deal_custom_field.rb new file mode 100644 index 0000000..8c864d6 --- /dev/null +++ b/app/models/deal_custom_field.rb @@ -0,0 +1,7 @@ +class DealCustomField < CustomField + unloadable + + def type_name + :label_deal_plural + end +end \ No newline at end of file diff --git a/app/models/deal_note.rb b/app/models/deal_note.rb new file mode 100644 index 0000000..0f1146c --- /dev/null +++ b/app/models/deal_note.rb @@ -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 diff --git a/app/models/deal_process.rb b/app/models/deal_process.rb new file mode 100644 index 0000000..aef45b7 --- /dev/null +++ b/app/models/deal_process.rb @@ -0,0 +1,7 @@ +class DealProcess < ActiveRecord::Base + unloadable + + belongs_to :author, :class_name => "User", :foreign_key => "author_id" + belongs_to :deal + +end diff --git a/app/models/deal_status.rb b/app/models/deal_status.rb new file mode 100644 index 0000000..7bb0096 --- /dev/null +++ b/app/models/deal_status.rb @@ -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 diff --git a/app/models/note.rb b/app/models/note.rb new file mode 100644 index 0000000..fe15bf2 --- /dev/null +++ b/app/models/note.rb @@ -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 + + diff --git a/app/models/note_custom_field.rb b/app/models/note_custom_field.rb new file mode 100644 index 0000000..4ba92c3 --- /dev/null +++ b/app/models/note_custom_field.rb @@ -0,0 +1,7 @@ +class NoteCustomField < CustomField + unloadable + + def type_name + :label_note_plural + end +end \ No newline at end of file diff --git a/app/models/recently_viewed.rb b/app/models/recently_viewed.rb new file mode 100644 index 0000000..19a91d3 --- /dev/null +++ b/app/models/recently_viewed.rb @@ -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 diff --git a/app/models/task.rb b/app/models/task.rb new file mode 100644 index 0000000..008094e --- /dev/null +++ b/app/models/task.rb @@ -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 diff --git a/app/views/auto_completes/_tag_list.html.erb b/app/views/auto_completes/_tag_list.html.erb new file mode 100644 index 0000000..aed55a4 --- /dev/null +++ b/app/views/auto_completes/_tag_list.html.erb @@ -0,0 +1,6 @@ + diff --git a/app/views/common/_contact_data.html.erb b/app/views/common/_contact_data.html.erb new file mode 100644 index 0000000..a9e079b --- /dev/null +++ b/app/views/common/_contact_data.html.erb @@ -0,0 +1,17 @@ +<% actions ||= "" %> + + + + + <% if !actions.blank? %> + + <% end %> + +
<%= link_to avatar_to(contact_data, :size => "32"), note_source_url(contact_data), :id => "avatar" %> +

+ <%= link_to contact_data.name, note_source_url(contact_data) %> +

+ <%= contact_data.info %> +
+ <%= actions %> +
diff --git a/app/views/common/_notes_attachments.html.erb b/app/views/common/_notes_attachments.html.erb new file mode 100644 index 0000000..49ff42b --- /dev/null +++ b/app/views/common/_notes_attachments.html.erb @@ -0,0 +1,4 @@ +<% if notes_attachments.any? %> +

<%= l(:label_attachment_plural) %>

+<%= render :partial => 'attachments/links', :locals => {:attachments => notes_attachments, :options => {}} %> +<% end %> diff --git a/app/views/common/_recently_viewed.html.erb b/app/views/common/_recently_viewed.html.erb new file mode 100644 index 0000000..939d9bf --- /dev/null +++ b/app/views/common/_recently_viewed.html.erb @@ -0,0 +1,4 @@ +

<%= l(:label_recently_viewed) %>

+
+ <%= render :partial => 'common/contact_data', :collection => RecentlyViewed.last(5) %> +
diff --git a/app/views/common/_responsible_user.html.erb b/app/views/common/_responsible_user.html.erb new file mode 100644 index 0000000..931a2e8 --- /dev/null +++ b/app/views/common/_responsible_user.html.erb @@ -0,0 +1,10 @@ +<% if responsible_user.assigned_to %> +

<%= l(:label_assigned_to) %>

+
+ +
+<% end %> diff --git a/app/views/common/_sidebar.html.erb b/app/views/common/_sidebar.html.erb new file mode 100644 index 0000000..db564f1 --- /dev/null +++ b/app/views/common/_sidebar.html.erb @@ -0,0 +1,17 @@ +<%= call_hook(:view_contacts_sidebar_top) %> + +

<%= l(:label_task_plural) %>

+<%= 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) %> \ No newline at end of file diff --git a/app/views/contacts/_attributes.html.erb b/app/views/contacts/_attributes.html.erb new file mode 100644 index 0000000..8ab4435 --- /dev/null +++ b/app/views/contacts/_attributes.html.erb @@ -0,0 +1,68 @@ +
+ +

<%= if !@contact.is_company then l(:label_contact) else l(:label_company) end %>

+ + + <%= call_hook(:view_contacts_sidebar_attributes_top) %> + + + + <% if !@contact.job_title.blank? %> + + <% end %> + <% if !@contact.is_company %> + + <% end %> + + + + + + + + + + + + + + + + + <% if !@contact.skype_name.blank? %> + + + + + <% end %> + <% if !@contact.birthday.blank? %> + + + <% end %> + + <% @contact.custom_values.each do |custom_value| %> + <% if !custom_value.value.blank? %> + + <% end %> + <% end %> + + + + <%= call_hook(:view_contacts_sidebar_attributes_bottom) %> + +
<%= l(:field_contact_name) %>:"><%= h @contact.name(:firstname_middlename_lastname) %>
<%= !@contact.is_company ? l(:field_contact_job_title) : l(:field_company_field) %>:<%= h @contact.job_title %>
<%=l(:field_contact_company)%>:<%= h @contact.company %>
<%= l(:field_contact_address) %>:<%= h @contact.address %> + <% if !@contact.address.blank? %> +
+ <%= 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 %> +
<%= l(:field_contact_phone) %>: + <% @contact.phones.each do |phone| %> + <%= h phone %>
+ <% end %> +
<%= l(:field_contact_email) %>: + <% @contact.emails.each do |email| %> + + <% end %> +
<%= l(:field_contact_website) %>:<%= link_to @contact.website, @contact.website_address %>
<%= l(:field_contact_skype) %>:<%=skype_to @contact.skype_name %>
<%= l(:field_birthday) %>:><%= "#{@contact.birthday.day} #{t('date.month_names')[@contact.birthday.month]}"%>
<%= l(:field_age) %>:<%= @contact.age %>
<%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %>
<%=l(:label_assigned_to)%>:<%= avatar(@contact.assigned_to, :size => "14") %><%= link_to_user(@contact.assigned_to) %>
+ +
\ No newline at end of file diff --git a/app/views/contacts/_employees.html.erb b/app/views/contacts/_employees.html.erb new file mode 100644 index 0000000..51e314f --- /dev/null +++ b/app/views/contacts/_employees.html.erb @@ -0,0 +1,10 @@ +<% if @contact.is_company %> +
+
+ <%= link_to_if_authorized l(:label_add_employee), {:controller => 'contacts', :action => 'new', :project_id => @project, :contact => {:company => @contact.name}} %> +
+

<%= l(:label_company_employees) %>

+ <%= render :partial => 'common/contact_data', :collection => @contact.employees %> +
+ +<% end %> diff --git a/app/views/contacts/_form.html.erb b/app/views/contacts/_form.html.erb new file mode 100644 index 0000000..3d90288 --- /dev/null +++ b/app/views/contacts/_form.html.erb @@ -0,0 +1,87 @@ +<%= error_messages_for 'contact' %> + + + +
+ + + +

+ <%= 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? %> +

+

<%= label_tag l(:field_contact_avatar) %> <%= file_field_tag 'contact_avatar[file]', :size => 30, :id => nil -%>

+

<%= f.check_box(:is_company, :label => l(:field_contact_is_company), :onclick => "togglePerson(this)" ) %>

+

<%= f.text_field :first_name, :label => !@contact.is_company ? l(:field_contact_first_name) : l(:field_company_name), :required => true, :size => 80%>

+ <% if @contact.is_company %> + + + + + diff --git a/app/views/contacts/_form_tags.html.erb b/app/views/contacts/_form_tags.html.erb new file mode 100644 index 0000000..c675e1d --- /dev/null +++ b/app/views/contacts/_form_tags.html.erb @@ -0,0 +1,22 @@ +
+ + <%= render :partial => 'contacts/tags_item', :collection => @contact.tags, :locals => {:is_note => false} %> + + <% if authorize_for('contacts', 'edit_tags') %> + + <%= link_to l(:label_edit_tags), {}, :onclick => "Element.show('edit_tags_form'); Element.hide('tags_data'); return false;", :id => 'edit_tags_link' %> + + <% end %> +
+ \ No newline at end of file diff --git a/app/views/contacts/_list.html.erb b/app/views/contacts/_list.html.erb new file mode 100644 index 0000000..78f92de --- /dev/null +++ b/app/views/contacts/_list.html.erb @@ -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? %> + + + <% @contacts.each do |contact| %> + + + + + + + <% end %> + +
+ <%= check_box_tag "selected_contacts[]", contact.id, false, :onclick => "toggleContact(event, this);" %> + + <%= link_to avatar_to(contact, :size => "32"), {:controller => 'contacts', :action => 'show', :project_id => @project, :id => contact.id}, :id => "avatar" %> + +

<%= link_to contact.name, contact_url(contact) %>

+

+ <%= link_to h(contact.website), contact.website_address, :only_path => true unless !contact.is_company %> + <%= mail_to contact.emails.first unless contact.is_company%> +
<%= contact.phones.first %>
+

+
+
+ <%= 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 %> +
+
+ <%= render :partial => "tags_item", :collection => contact.tags, :locals => {:is_note => false} %> +
+
+ <%= contacts_paginator @contacts_pages, :params => {:project_id => params[:project_id]} if @contacts_pages %> + <% else %> +

<%=l(:label_no_data)%>

+ <% end %> +<% end %> + +<%= context_menu url_for( {:controller => "contacts", :action => "context_menu"} )%> diff --git a/app/views/contacts/_name_observer.html.erb b/app/views/contacts/_name_observer.html.erb new file mode 100644 index 0000000..35226ca --- /dev/null +++ b/app/views/contacts/_name_observer.html.erb @@ -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()") %> + diff --git a/app/views/contacts/_tags_cloud.html.erb b/app/views/contacts/_tags_cloud.html.erb new file mode 100644 index 0000000..7cdc8f8 --- /dev/null +++ b/app/views/contacts/_tags_cloud.html.erb @@ -0,0 +1,21 @@ +
+ 1) %> > + > + <%= link_to l(:label_multiple_tags_mode), {}, :onclick => "$('multiple_tags').toggle(); $('single_tags').toggle(); return false;", :id => 'edit_tags_link' %> + + +

<%= l(:label_tags_plural) %>

+ <%= render :partial => "tags_item", :collection => @tags, :locals => {:is_note => false, :multiple => false, :show_selected => true} %> +
+ + > + + <%= link_to l(:label_single_tag_mode), {}, :onclick => "$('multiple_tags').toggle(); $('single_tags').toggle(); return false;", :id => 'edit_tags_link' %> + + +

<%= l(:label_multi_tags_plural) %>

+ <%= render :partial => "tags_item", :collection => @tags, :locals => {:is_note => false, :multiple => true, :show_selected => true} %> +
+ +
+ diff --git a/app/views/contacts/_tags_item.html.erb b/app/views/contacts/_tags_item.html.erb new file mode 100644 index 0000000..148849d --- /dev/null +++ b/app/views/contacts/_tags_item.html.erb @@ -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 +%> + + + <%- 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 -%> + + diff --git a/app/views/contacts/bulk_edit.html.erb b/app/views/contacts/bulk_edit.html.erb new file mode 100644 index 0000000..d1e9dcb --- /dev/null +++ b/app/views/contacts/bulk_edit.html.erb @@ -0,0 +1,109 @@ +

<%= l(:label_bulk_edit_selected_contacts) %>

+ + +
+ +
+ + +<% form_tag(:action => 'bulk_update') do %> +<%= @contacts.collect {|i| hidden_field_tag('ids[]', i.id)}.join %> +
+
+<%= l(:label_change_properties) %> + +

+ + <%= text_field_tag('contact[company]', '') %> +

+ +

+ + <%= text_field_tag('contact[job_title]', '') %> +

+ +<% @contacts.first.custom_field_values.each do |value| %> +

+ <% value.value = '' %> + <%= custom_field_tag_with_label :contact, value %> +

+<% end -%> + + +

+ + <%= 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)) %> +

+ +
+ +
+<%= l(:label_tags_plural) %> + +
+

+ + <%= text_field_tag 'add_tag_list', '', :label => :field_contact_tag_names, :size => 10, :class => 'hol', :style => "display: none;" %> +

+
+ <%= javascript_tag "observeTagsField('#{url_for(:controller => 'auto_completes', :action => 'contact_tags', :project_id => @project)}', 'add_tag_list', 'add_tag_candidates')" %> +
+
+

+ + <%= text_field_tag 'delete_tag_list', '', :label => :field_contact_tag_names, :size => 10, :class => 'hol', :style => "display: none;" %> +

+
+ <%= javascript_tag "observeTagsField('#{url_for(:controller => 'auto_completes', :action => 'contact_tags', :project_id => @project)}', 'delete_tag_list', 'delete_tag_candidates')" %> +
+
+ +<% if @add_projects.any? %> +
+<%= l(:label_project_plural) %> +

+ + <%= 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 %> +

+ +

+ + <%= 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 %> +

+ + +
+<% end %> + + + +
<%= l(:field_notes) %> +<%= text_area_tag 'note[content]', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %> +<%= wikitoolbar_for 'note_content' %> +
+
+ +

<%= submit_tag l(:button_submit) %>

+<% end %> + +<% content_for :header_tags do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> diff --git a/app/views/contacts/contacts_notes.html.erb b/app/views/contacts/contacts_notes.html.erb new file mode 100644 index 0000000..38ffe3b --- /dev/null +++ b/app/views/contacts/contacts_notes.html.erb @@ -0,0 +1,87 @@ +
+<% if !@tag %> + + <% form_tag(params, :id => "query_form") do %> + <%= hidden_field_tag('project_id', @project.to_param) if @project %> +

+ + <%= l(:label_contact_note_plural) %> + + + <%= 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')") %> + + + +

+ + <% if false %> + +

+ + <%= 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' %> +

+ + <% end %> + + <% end %> + + + +<% else %> +

<%= "#{l(:label_contact_tag)}(#{@notes_pages.item_count}): #{render(:partial => "tags_item", :collection => @tag, :locals => {:is_note => false} )}" %>

+<% end %> +
+ +
+ <%= render :partial => 'notes/notes_list' %> +
+ + +<% content_for :sidebar do %> + <%= render :partial => 'common/sidebar' %> + + +

<%= l(:label_tags_plural) %>

+
+ <%= render :partial => "tags_item", :collection => @tags, :locals => {:is_note => true, :multiple => false} %> +
+ + <%= 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 %> + + + + diff --git a/app/views/contacts/context_menu.html.erb b/app/views/contacts/context_menu.html.erb new file mode 100644 index 0000000..f3e9dc1 --- /dev/null +++ b/app/views/contacts/context_menu.html.erb @@ -0,0 +1,28 @@ + + diff --git a/app/views/contacts/edit.html.erb b/app/views/contacts/edit.html.erb new file mode 100644 index 0000000..bcca508 --- /dev/null +++ b/app/views/contacts/edit.html.erb @@ -0,0 +1,25 @@ +
+ <%= link_to_if_authorized l(:label_contact_new), {:controller => 'contacts', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %> +
+ +

<%= l(:label_contact_edit_information) %>

+ +<% 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 %> diff --git a/app/views/contacts/edit_mails.html.erb b/app/views/contacts/edit_mails.html.erb new file mode 100644 index 0000000..4aae2a9 --- /dev/null +++ b/app/views/contacts/edit_mails.html.erb @@ -0,0 +1,65 @@ +

<%= l(:label_bulk_send_mail_selected_contacts) %>

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

+ + <%= text_field_tag('from', User.current.mail, :id => "email", :size => 30) %> +

+ + + +

+ + <%= text_field_tag('subject', '', :id => "subject", :size => 100) %> +

+

+ + <%= text_area_tag 'message-content', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %> + <%= l(:text_email_macros, :macro => "%%NAME%%, %%LAST_NAME%%, %%MIDDLE_NAME%%, %%FULL_NAME%%, %%COMPANY%%, %%DATE%%, %%[Custom field]%%") %> +

+ <%= wikitoolbar_for 'message-content' %> + +

<%= label_tag('attachments[1][file]', l(:label_attachment_plural))%><%= render :partial => 'attachments/form' %>

+
+ +

+ <%= 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) %> + +

+ +<% end %> + +
+ +<% content_for :header_tags do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> diff --git a/app/views/contacts/index.api.rsb b/app/views/contacts/index.api.rsb new file mode 100644 index 0000000..ff6e452 --- /dev/null +++ b/app/views/contacts/index.api.rsb @@ -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 \ No newline at end of file diff --git a/app/views/contacts/index.html.erb b/app/views/contacts/index.html.erb new file mode 100644 index 0000000..f8fba04 --- /dev/null +++ b/app/views/contacts/index.html.erb @@ -0,0 +1,82 @@ +
+ <%= link_to_if_authorized l(:label_contact_new), {:controller => 'contacts', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %> +
+ +
+

+ + + + <%= link_to "#{contacts_filter_name(params[:query])}(#{@contacts_count})", {}, :onclick => "Element.toggle('select_scope'); Element.toggle('scope_header'); return false;" %> + + + <% if !@tag %> + + <%= 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');}" ) %> + + <%= observe_field("search", + :frequency => 2, + :update => 'contact_list', + :url => {:controller => 'contacts', :action => 'index', :project_id => @project }, + :with => "search") %> + + <% end %> + + + <%= render(:partial => "tags_item", :collection => @tag, :locals => {:is_note => true} ) %> + + +

+ + <% unless true %> + +
+ <%= l(:label_filter_plural) %> +
+ <%= select_tag :deal_status_id, options_from_collection_for_select(@project.assignable_users, :id, :name), :onchange => "this.form.submit();" %> +
+
+ + <% end %> + +
+
+ <%= render :partial => 'list' %> +
+ +<% 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 %> + diff --git a/app/views/contacts/new.html.erb b/app/views/contacts/new.html.erb new file mode 100644 index 0000000..eafb7b4 --- /dev/null +++ b/app/views/contacts/new.html.erb @@ -0,0 +1,20 @@ +

<%= l(:label_contact_new) %>

+ +<% 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 %> diff --git a/app/views/contacts/show.api.rsb b/app/views/contacts/show.api.rsb new file mode 100644 index 0000000..1c22b23 --- /dev/null +++ b/app/views/contacts/show.api.rsb @@ -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 diff --git a/app/views/contacts/show.html.erb b/app/views/contacts/show.html.erb new file mode 100644 index 0000000..05d86c8 --- /dev/null +++ b/app/views/contacts/show.html.erb @@ -0,0 +1,120 @@ +<%= error_messages_for 'contact', 'note' %> +<%= error_messages_for %> + +<% html_title "#{l(:label_contact)} ##{@contact.id}: #{@contact.name}" %> + +
+ <% 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? %> +
+ +

<%= !@contact.is_company ? l(:label_contact) : l(:label_company) %> #<%= @contact.id %>

+<% unless @contact.nil? -%> +
+ + + + + <% if @contact.phones.any? || @contact.emails.any? %> + + <% end %> + +
<%= avatar_to(@contact, :size => "64", :full_size => true) %> +

<%= h @contact.name %>

+ <% if !@contact.is_company %> +

+ <%= 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 %> +

+ <% end %> + + <%= render :partial => 'form_tags' %> + +
+
    + <% if @contact.phones.any? %> +
  • <%= @contact.phones.first %>
  • + <% end %> + + <% if @contact.emails.any? %> + + <% end %> +
+
+ + <%= call_hook(:view_contacts_show_details_bottom, :contact => @contact) %> + + <% if authorize_for(:notes, :add_note) %> +
+ <%= render :partial => 'notes/add', :locals => {:note_source => @contact} %> + <% end %> + +
+ + + +<% if authorize_for(:notes, :show) %> +
+

<%= l(:label_note_plural) %>

+
+ <%= render :partial => 'notes/note_item', :collection => @notes, :locals => {:show_info => @contact.is_company, :note_source => @contact} %> +

<%= pagination_links @notes_pages, :params => {:project_id => params[:project_id]}%>

+
+
+<% end %> + +<% else %> +

<%=l(:label_no_data)%>

+<% 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? %> +

<%= l(:label_contact_background_info) %>

+
<%= textilizable(@contact, :background) %>
+ <% 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 -%> + +<% 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 %> \ No newline at end of file diff --git a/app/views/contacts_duplicates/_duplicates.html.erb b/app/views/contacts_duplicates/_duplicates.html.erb new file mode 100644 index 0000000..a190863 --- /dev/null +++ b/app/views/contacts_duplicates/_duplicates.html.erb @@ -0,0 +1,23 @@ +
+<% if @contact.duplicates.any? %> + <% if !@contact.new_record? %> +
+ <%= link_to_if_authorized l(:label_merge_dublicate_plural), {:controller => 'contacts_duplicates', :action => 'index', :project_id => @project, :contact_id => @contact} %> +
+ <% end %> + +

<%= l(:label_dublicate_plural) %>

+ + + +<% end %> +
diff --git a/app/views/contacts_duplicates/index.html.erb b/app/views/contacts_duplicates/index.html.erb new file mode 100644 index 0000000..4d9380e --- /dev/null +++ b/app/views/contacts_duplicates/index.html.erb @@ -0,0 +1,59 @@ +<%= breadcrumb link_to(@contact.name, note_source_url(@contact)) %> + +
+ + + + + + +
<%= link_to avatar_to(@contact, :size => "32"), note_source_url(@contact), :id => "avatar" %> +

+ <%= l(:label_dublicate_for_plural) %>: <%= @contact.name %> +

+

+ <%= 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 %> +

+ +
+
+ +<% form_tag({:controller => 'contacts_duplicates', :action => 'merge', :project_id => @project, :contact_id => @contact}) do %> +
+ <%= content_tag('div', l(:notice_merged_warning), :class => "flash warning") %> + + +
+ <%= 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 %> diff --git a/app/views/contacts_mailer/bulk_mail.text.html.rhtml b/app/views/contacts_mailer/bulk_mail.text.html.rhtml new file mode 100644 index 0000000..8766b43 --- /dev/null +++ b/app/views/contacts_mailer/bulk_mail.text.html.rhtml @@ -0,0 +1 @@ +<%= textilizable(@params[:message], :only_path => false).gsub(/¶/, '') %> \ No newline at end of file diff --git a/app/views/contacts_mailer/bulk_mail.text.plain.rhtml b/app/views/contacts_mailer/bulk_mail.text.plain.rhtml new file mode 100644 index 0000000..05805b8 --- /dev/null +++ b/app/views/contacts_mailer/bulk_mail.text.plain.rhtml @@ -0,0 +1 @@ +<%= @params[:message] %> \ No newline at end of file diff --git a/app/views/contacts_projects/_related.html.erb b/app/views/contacts_projects/_related.html.erb new file mode 100644 index 0000000..8e1b78e --- /dev/null +++ b/app/views/contacts_projects/_related.html.erb @@ -0,0 +1,30 @@ +
+
+ <%= link_to_remote(l(:button_add), + :url => {:controller => 'contacts_projects', + :action => 'add', + :project_id => @project, + :contact_id => @contact}) if authorize_for(:contacts, :edit) %> + +
+ + +

<%= l(:label_project_plural) %>

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

<%= 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'%>

+ <% end %> + <% end %> + + <%= render_contact_projects_hierarchy @contact.projects %> + +
diff --git a/app/views/contacts_tags/_tags_form.html.erb b/app/views/contacts_tags/_tags_form.html.erb new file mode 100644 index 0000000..0a385f4 --- /dev/null +++ b/app/views/contacts_tags/_tags_form.html.erb @@ -0,0 +1,10 @@ +<% fields_for :contact, @contact do |f| -%> +
+

+ + <%= f.text_field :tag_list, :label => :field_contact_tag_names, :size => 10, :class => 'hol' %> +

+
+ <%= javascript_tag "observeContactTagsField('#{url_for(:controller => 'auto_completes', :action => 'contact_tags', :project_id => @project)}')" %> +
+<% end -%> diff --git a/app/views/contacts_tags/edit.html.erb b/app/views/contacts_tags/edit.html.erb new file mode 100644 index 0000000..63f168a --- /dev/null +++ b/app/views/contacts_tags/edit.html.erb @@ -0,0 +1,32 @@ +

<%= link_to l(:contacts_title), :controller => 'settings', :action => 'plugin', :id => 'contacts', :tab => 'tags' %> » <%=h (l(:label_tag) + ": " + @tag.name) %>

+ +<% form_tag({:action => 'update', :id => @tag, :tab => 'tags' }, :class => "tabular") do %> + <%= error_messages_for 'tag' %> +
+ +

+<%= text_field 'tag', 'name' %>

+ +

+<%= text_field 'tag', 'color_name', :class => "colorpicker" %> + +

+ + + +
+ + <%= 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 %> diff --git a/app/views/contacts_tags/index.html.erb b/app/views/contacts_tags/index.html.erb new file mode 100644 index 0000000..9895d0e --- /dev/null +++ b/app/views/contacts_tags/index.html.erb @@ -0,0 +1 @@ +

ContactsTags#index

diff --git a/app/views/contacts_tasks/_attributes.html.erb b/app/views/contacts_tasks/_attributes.html.erb new file mode 100644 index 0000000..e4d3101 --- /dev/null +++ b/app/views/contacts_tasks/_attributes.html.erb @@ -0,0 +1,28 @@ +<% fields_for "issue" do |ff| %> + + <%= label_tag 'task_subject', l(:field_subject)%>
+ <%= ff.text_field :subject, :style => "width: 98%;" %> + +

+ <%= label_tag :assigned_to_id, l(:field_assigned_to)%>
+ <%= ff.select :assigned_to_id, @project.assignable_users.collect {|m| [m.name, m.id]}, :selected => User.current.id, :include_blank => true %> +

+ <%= label_tag 'due_date', l(:field_due_date)%>
+ <%= ff.text_field :due_date, :value => Date.today, :size => 12 %><%= calendar_for('issue_due_date') %>
+ +

+ <%= label_tag :description, l(:field_description)%>
+ <%= ff.text_area :description, :value => "", :rows => 6, :class => 'wiki-edit' , :style => "width: 98%;" %>
+

+ +

+ <%= label_tag :tracker_id, l(:field_tracker)%>
+ <%= ff.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]} %> +

+ +<% end %> + +
+
+
+<%= submit_tag l(:button_add) %> diff --git a/app/views/contacts_tasks/_related.html.erb b/app/views/contacts_tasks/_related.html.erb new file mode 100644 index 0000000..3e0e8a0 --- /dev/null +++ b/app/views/contacts_tasks/_related.html.erb @@ -0,0 +1,43 @@ +
+ +
+ <%= 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) %> +
+ +

<%= l(:label_issue_plural) %>

+ + +<%= error_messages_for 'issue' %> + + + + +
+ + <% if tasks.any? %> + + <% tasks.each do |issue| %> + > + + + + + + <% end %> +
+ <%= 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}") %> + + <%= link_to issue.subject, :controller => :issues, :action => :show, :id => issue %> + <%= format_date(issue.due_date) %>
+ <% end %> +
+ +
diff --git a/app/views/contacts_tasks/index.html.erb b/app/views/contacts_tasks/index.html.erb new file mode 100644 index 0000000..f05f009 --- /dev/null +++ b/app/views/contacts_tasks/index.html.erb @@ -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 %> + +

<%= l(:label_issue_plural) %>

+<% form_tag({:project_id => @project}, :id => 'query_form') do %> +

+ <%= label_tag l(:label_assigned_to) %> + <%= select_tag :assigned_to, options_for_select(@users, params[:assigned_to]), :onchange => "this.form.submit();" %> +

+<% end %> + +<% unless @contacts_issues.empty? %> + + + <% @contacts_issues.select {|i| !i.closed?}.each do |issue| %> + > + + + + + <% end %> + +
+ <%= 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 %> + + <%= link_to issue.subject, :controller => :issues, :action => :show, :id => issue %> + <%= "(#{link_to_source issue.contacts.first})" %> + <%= format_date(issue.due_date) %>
+<% else %> +

<%=l(:label_no_data)%>

+<% end %> + diff --git a/app/views/contacts_vcf/_load.html.erb b/app/views/contacts_vcf/_load.html.erb new file mode 100644 index 0000000..8521895 --- /dev/null +++ b/app/views/contacts_vcf/_load.html.erb @@ -0,0 +1,16 @@ +<%- if Gem.available?('vpim') -%> +

<%= l(:label_import) %>

+ + + <%= link_to l(:label_vcf_import), {}, :onclick => "Element.toggle('import_link'); Element.toggle('import_file'); return false;" %> + + <% form_tag({:controller => :contacts_vcf, :action => :load, :project_id => @project}, :multipart => true ) do %> + + <% end %> + +<%- end -%> diff --git a/app/views/deal_categories/_form.html.erb b/app/views/deal_categories/_form.html.erb new file mode 100644 index 0000000..dd4545d --- /dev/null +++ b/app/views/deal_categories/_form.html.erb @@ -0,0 +1,5 @@ +<%= error_messages_for 'category' %> + +
+

<%= f.text_field :name, :size => 30, :required => true %>

+
\ No newline at end of file diff --git a/app/views/deal_categories/destroy.html.erb b/app/views/deal_categories/destroy.html.erb new file mode 100644 index 0000000..fb66a6c --- /dev/null +++ b/app/views/deal_categories/destroy.html.erb @@ -0,0 +1,15 @@ +

<%=l(:label_deal_category)%>: <%=h @category.name %>

+ +<% form_tag({}) do %> +
+

<%= l(:text_deal_category_destroy_question, @deal_count) %>

+


+<% if @categories.size > 0 %> +: +<%= select_tag 'reassign_to_id', options_from_collection_for_select(@categories, 'id', 'name') %>

+<% end %> +
+ +<%= submit_tag l(:button_apply) %> +<%= link_to l(:button_cancel), :controller => 'projects', :action => 'settings', :id => @project, :tab => 'contacts' %> +<% end %> \ No newline at end of file diff --git a/app/views/deal_categories/edit.html.erb b/app/views/deal_categories/edit.html.erb new file mode 100644 index 0000000..7823463 --- /dev/null +++ b/app/views/deal_categories/edit.html.erb @@ -0,0 +1,6 @@ +

<%=l(:label_deal_category)%>

+ +<% 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 %> \ No newline at end of file diff --git a/app/views/deal_categories/index.html.erb b/app/views/deal_categories/index.html.erb new file mode 100644 index 0000000..735f8cc --- /dev/null +++ b/app/views/deal_categories/index.html.erb @@ -0,0 +1 @@ +

DealCategories#index

diff --git a/app/views/deal_categories/new.html.erb b/app/views/deal_categories/new.html.erb new file mode 100644 index 0000000..4ff6924 --- /dev/null +++ b/app/views/deal_categories/new.html.erb @@ -0,0 +1,6 @@ +

<%=l(:label_issue_category_new)%>

+ +<% 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 %> \ No newline at end of file diff --git a/app/views/deal_contacts/_contacts.html.erb b/app/views/deal_contacts/_contacts.html.erb new file mode 100644 index 0000000..70b908f --- /dev/null +++ b/app/views/deal_contacts/_contacts.html.erb @@ -0,0 +1,36 @@ +<% if @deal.all_contacts.any? %> +
+
+ <%= 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) %> + +
+ +

<%= l(:label_contractor_plural) %>

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

+ <%= 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'%> +

+ <% 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 %> +
+<% end %> \ No newline at end of file diff --git a/app/views/deal_statuses/_form.html.erb b/app/views/deal_statuses/_form.html.erb new file mode 100644 index 0000000..16e9060 --- /dev/null +++ b/app/views/deal_statuses/_form.html.erb @@ -0,0 +1,35 @@ +<%= error_messages_for 'deal_status' %> + +
+ +

+<%= text_field 'deal_status', 'name' %>

+ + +

+<%= text_field 'deal_status', 'color_name', :class => "colorpicker" %> + +

+ + + +

+<%= check_box 'deal_status', 'is_closed' %>

+ +

+<%= check_box 'deal_status', 'is_default' %>

+ +<%= call_hook(:view_deal_statuses_form, :deal_status => @deal_status) %> + + +
+ + + +<% content_for :header_tags do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> diff --git a/app/views/deal_statuses/edit.html.erb b/app/views/deal_statuses/edit.html.erb new file mode 100644 index 0000000..b83b0a6 --- /dev/null +++ b/app/views/deal_statuses/edit.html.erb @@ -0,0 +1,6 @@ +

<%= link_to l(:label_deal_status_plural), :controller => 'deal_statuses', :action => 'index' %> » <%=h @deal_status %>

+ +<% form_tag({:action => 'update', :id => @deal_status}, :class => "tabular") do %> + <%= render :partial => 'form' %> + <%= submit_tag l(:button_save) %> +<% end %> diff --git a/app/views/deal_statuses/index.html.erb b/app/views/deal_statuses/index.html.erb new file mode 100644 index 0000000..9ba2958 --- /dev/null +++ b/app/views/deal_statuses/index.html.erb @@ -0,0 +1,35 @@ +
+<%= link_to l(:label_deal_status_new), {:action => 'new'}, :class => 'icon icon-add' %> +
+ +

<%=l(:label_deal_status_plural)%>

+ + + + + + + + + + +<% for status in @deal_statuses %> + "> + + + + + + +<% end %> + +
<%=l(:field_status)%><%=l(:field_is_default)%><%=l(:field_is_closed)%><%=l(:button_sort)%>
<%= link_to status.name, :action => 'edit', :id => status %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %><%= reorder_links('deal_status', {:action => 'update', :id => status}) %> + <%= link_to(l(:button_delete), { :action => 'destroy', :id => status }, + :method => :post, + :confirm => l(:text_are_you_sure), + :class => 'icon icon-del') %> +
+ +

<%= pagination_links_full @deal_status_pages %>

+ +<% html_title(l(:label_deal_status_plural)) -%> diff --git a/app/views/deal_statuses/new.html.erb b/app/views/deal_statuses/new.html.erb new file mode 100644 index 0000000..52969c8 --- /dev/null +++ b/app/views/deal_statuses/new.html.erb @@ -0,0 +1,6 @@ +

<%= link_to l(:label_deal_status_plural), :controller => 'deal_statuses', :action => 'index' %> » <%=l(:label_deal_status_new)%>

+ +<% form_tag({:action => 'create'}, :class => "tabular") do %> + <%= render :partial => 'form' %> + <%= submit_tag l(:button_create) %> +<% end %> diff --git a/app/views/deals/_attributes.html.erb b/app/views/deals/_attributes.html.erb new file mode 100644 index 0000000..53d232c --- /dev/null +++ b/app/views/deals/_attributes.html.erb @@ -0,0 +1,14 @@ +<% if @deal.custom_values.any? %> +
+ +

<%= l(:label_deal) %>

+ + <% @deal.custom_values.each do |custom_value| %> + <% if !custom_value.value.blank? %> + + <% end %> + <% end %> +
<%= custom_value.custom_field.name %>: <%=h show_value(custom_value) %>
+ +
+<% end %> diff --git a/app/views/deals/_custom_field_filter.html.erb b/app/views/deals/_custom_field_filter.html.erb new file mode 100644 index 0000000..3260a5e --- /dev/null +++ b/app/views/deals/_custom_field_filter.html.erb @@ -0,0 +1,5 @@ + + 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]) %> + diff --git a/app/views/deals/_custom_field_form.html.erb b/app/views/deals/_custom_field_form.html.erb new file mode 100644 index 0000000..191e09f --- /dev/null +++ b/app/views/deals/_custom_field_form.html.erb @@ -0,0 +1,3 @@ +

<%= form.check_box :is_for_all %>

+

<%= form.check_box :is_filter %>

+

<%= form.check_box :visible, :label => l(:label_contacts_show_in_list) %>

\ No newline at end of file diff --git a/app/views/deals/_deals_statistics.html.erb b/app/views/deals/_deals_statistics.html.erb new file mode 100644 index 0000000..4557eb1 --- /dev/null +++ b/app/views/deals/_deals_statistics.html.erb @@ -0,0 +1,26 @@ +<% if deal_statuses.any? %> +
+ <% if !(@project && !authorize_for(:sale_funel, :index)) %> +
+ <%= link_to l(:label_sale_funel), {:controller => 'sale_funel', :action => 'index', :project_id => @project} %> +
+ <% end %> +

<%= l(:label_statistics) %>

+ + <% deal_statuses.each do |deal_status| %> + + + + + <% end %> +
+ > + <%= h "#{deal_status.name}(#{@project ? @project.deals.count(:conditions => {:status_id => deal_status.id}) : Deal.count(:conditions => {:status_id => deal_status.id})})" %> + + + + <%= @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)) %> + +
+
+<% end %> \ No newline at end of file diff --git a/app/views/deals/_form.html.erb b/app/views/deals/_form.html.erb new file mode 100644 index 0000000..2236b6b --- /dev/null +++ b/app/views/deals/_form.html.erb @@ -0,0 +1,38 @@ +<%= error_messages_for 'deal' %> +
+

<%= f.text_field :name, :label=>l(:field_deal_name), :size => 80, :required => true %>

+ +

<%= f.text_area :background , :cols => 80, :rows => 8, :class => 'wiki-edit', :label=>l(:field_deal_background) %>

<%= wikitoolbar_for 'deal_background' %> + + <% if @project.deal_statuses.any? %> +

<%= f.select :status_id, collection_for_status_select, :include_blank => false, :selected => @deal.status_id.to_s, :label=>l(:field_contact_status) %>

+ <% end %> + <% unless @project.deal_categories.empty? %> +

<%= 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') %>

+ <% end %> +

+ <%= 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') %> +

+

+ <%= 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 %> + +

+ + <% @deal.custom_field_values.each do |value| %> +

+ <%= custom_field_tag_with_label :deal, value %> +

+ <% end -%> + +

<%= f.select :assigned_to_id, (@project.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true, :label => l(:label_assigned_to) %>

+
diff --git a/app/views/deals/_list.html.erb b/app/views/deals/_list.html.erb new file mode 100644 index 0000000..00f8070 --- /dev/null +++ b/app/views/deals/_list.html.erb @@ -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? %> + + + <% @deals.each do |deal| %> + + + + + + + + <% end %> + + + + + +
+ <%= check_box_tag "ids[]", deal.id, false, :onclick => "toggleContact(event, this);" %> + + <%= link_to avatar_to(deal, :size => "32"), {:controller => 'deals', :action => 'show', :id => deal.id}, :id => "avatar" %> + +

<%= link_to deal.name, :controller => 'deals', :action => 'show', :id => deal.id %>

+

+ <%= link_to_source(deal.contact) if deal.contact %> + +

+
+
<%= deal_price(deal) %> + <% if deal.status && deal.project.deal_statuses.any? %> + > + <%= h deal.status %> + + <% end %> +
+
+ <%= h deal.category %> +
+
+ + <%= "#{l(:label_total)} (#{@deals_count}):" %> + <%= deals_sum_to_currency(@deals_sum).gsub(' / ', '
') %> +
+ <%= contacts_paginator @deals_pages, :params => {:project_id => params[:project_id]} if @deals_pages %> + + <% else %> +

<%=l(:label_no_data)%>

+ <% end %> +<% end %> + +<%= context_menu url_for( {:controller => "deals", :action => "context_menu"} )%> diff --git a/app/views/deals/_related_deals.html.erb b/app/views/deals/_related_deals.html.erb new file mode 100644 index 0000000..2e4ffc2 --- /dev/null +++ b/app/views/deals/_related_deals.html.erb @@ -0,0 +1,28 @@ +
+ +
+ <%= link_to_if_authorized l(:label_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project, :contact_id => @contact} %> +
+ +

<%= "#{l(:label_deal_plural)}" %> <%= " - #{h number_to_currency(related_deals.sum{|d| d.price || 0})}" if false %>

+ +<% if related_deals.any? %> + + <% related_deals.each do |deal| %> + + + + <% end %> + +<% end %> +
\ No newline at end of file diff --git a/app/views/deals/bulk_edit.html.erb b/app/views/deals/bulk_edit.html.erb new file mode 100644 index 0000000..8aedd1f --- /dev/null +++ b/app/views/deals/bulk_edit.html.erb @@ -0,0 +1,80 @@ +

<%= l(:label_bulk_edit_selected_deals) %>

+ + +
+ +
+ + +<% form_tag(:action => 'bulk_update') do %> +<%= @deals.collect {|i| hidden_field_tag('ids[]', i.id)}.join %> +
+
+<%= l(:label_change_properties) %> + +<% if @available_statuses.any? %> +

+ + <%= select_tag('deal[status_id]', content_tag('option', l(:label_no_change_option), :value => '') + + options_from_collection_for_select(@available_statuses, :id, :name)) %> +

+<% end %> + + +<% if @available_categories.any? %> +

+ + <%= 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)) %> +

+<% end %> + +

+ + <%= 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)) %> +

+

+ + <%= select_tag "deal[currency]", options_for_select(collection_for_currencies_select.insert(0, ['', '']), '') %> + +

+ + +<% @deals.first.custom_field_values.each do |value| %> +

+ <% value.value = '' %> + <%= custom_field_tag_with_label :contact, value %> +

+<% end -%> + +
+ +
<%= l(:field_notes) %> +<%= text_area_tag 'note[content]', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %> +<%= wikitoolbar_for 'note_content' %> +
+
+ +

<%= submit_tag l(:button_submit) %>

+<% end %> + +<% content_for :header_tags do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> diff --git a/app/views/deals/context_menu.html.erb b/app/views/deals/context_menu.html.erb new file mode 100644 index 0000000..6f532d6 --- /dev/null +++ b/app/views/deals/context_menu.html.erb @@ -0,0 +1,46 @@ + + diff --git a/app/views/deals/edit.html.erb b/app/views/deals/edit.html.erb new file mode 100644 index 0000000..0adc667 --- /dev/null +++ b/app/views/deals/edit.html.erb @@ -0,0 +1,6 @@ +

<%= l(:label_deal_edit_information) %>

+ +<% labelled_form_for :deal, @deal, :url => {:action => 'update', :id => @deal} do |f| %> + <%= render :partial => 'form', :locals => {:f => f} %> + <%= submit_tag l(:button_save) -%> +<% end -%> diff --git a/app/views/deals/index.html.erb b/app/views/deals/index.html.erb new file mode 100644 index 0000000..aa3d8f7 --- /dev/null +++ b/app/views/deals/index.html.erb @@ -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' %> + +<% 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 %> + +
+ <%= link_to_if_authorized l(:label_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project}, :class => 'icon icon-add' %> +
+ +
+ <% 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?) %> + +

+ + <%= l(:label_deal_plural) %> + + + + <%= 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');}" ) %> + + <%= observe_field("search", + :frequency => 2, + :update => 'contact_list', + :url => {:controller => 'deals', :action => 'index', :project_id => @project }, + :with => "Form.serialize('query_form')") %> + + +

+ + + +
+ <%= l(:label_filter_plural) %> +
+

+ <% if !deal_statuses.empty? %> + + <%= 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]) %> + + <% end %> + + <%= label_tag l(:label_created_on) + " "%> + <%= select_tag 'period', options_for_period_select(params[:period]) %> + + <% if @project && !@project.deal_categories.empty? %> + + <%= 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]) %> + + <% end %> + + <%= 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]) %> + + + + +

+ +
+
+

+ + <%= 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' %> +

+ <% end %> + +
+ + +
+ <%= render :partial => 'list' %> +
+ +<% html_title l(:label_deal_plural) %> + diff --git a/app/views/deals/new.html.erb b/app/views/deals/new.html.erb new file mode 100644 index 0000000..1bbdfb2 --- /dev/null +++ b/app/views/deals/new.html.erb @@ -0,0 +1,6 @@ +

<%= l(:label_deal_new) %>

+ +<% 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 -%> diff --git a/app/views/deals/show.html.erb b/app/views/deals/show.html.erb new file mode 100644 index 0000000..46710c0 --- /dev/null +++ b/app/views/deals/show.html.erb @@ -0,0 +1,94 @@ +
+ <% 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? %> +
+

<%= "#{l(:label_deal)} ##{@deal.id}" %>

+
+ + + + + + <% if !@deal.price.blank? %> + + <% end %> + + +
<%= avatar_to(@deal, :size => "64") %> +

<%= h @deal.contact.name + ": " if @deal.contact %> <%= @deal.name %>

+

<%= h @deal.category %>

+ <% if @deal.status && @project.deal_statuses.any? %> +
+ > + <%= h @deal.status %> + + <% if authorize_for('deals', 'edit') %> + + <%= link_to l(:label_deal_change_status), {}, :onclick => "Element.show('edit_status_form'); Element.hide('deal-status'); return false;", :id => 'edit_status_link' %> + + <% end %> +
+ <% 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;" %> +
+ + <% end %> + <% end %> +
+
    +
  • <%= deal_price(@deal) %>
  • +
+
+ + + <% if authorize_for('notes', 'add_note') %> +
+ <%= render :partial => 'notes/add', :locals => {:note_source => @deal} %> + <% end %> +
+ +
+

<%= l(:label_note_plural) %>

+
+ <%= render :partial => 'notes/note_item', :collection => @deal.notes, :locals => {:note_source => @deal} %> +
+ +
+ +<% 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? %> +

<%= l(:label_contact_background_info) %>

+
<%= textilizable(@deal, :background) %>
+ <% 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' %> + +<% end %> \ No newline at end of file diff --git a/app/views/issues/_contacts.html.erb b/app/views/issues/_contacts.html.erb new file mode 100644 index 0000000..ad8b638 --- /dev/null +++ b/app/views/issues/_contacts.html.erb @@ -0,0 +1,72 @@ +<% if !@issue.blank? && (User.current.allowed_to?(:view_contacts, @project) || User.current.admin?) %> + +
+ + +
+ <%= 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) %> + +
+ + +

<%= l(:label_contact_plural) %>

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

<%= 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'%>

+ <% end %> + <% end %> + + + + +
+ + + +<% end %> + + + diff --git a/app/views/issues/_taskables.html.erb b/app/views/issues/_taskables.html.erb new file mode 100644 index 0000000..18f9853 --- /dev/null +++ b/app/views/issues/_taskables.html.erb @@ -0,0 +1,78 @@ +<% source_type %> + +<% if !@issue.blank? && (User.current.allowed_to?("view_#{source_type.name}s".to_sym, @project) || User.current.admin?) %> + +
+ + +
+ <%= link_to_remote l(:button_add), + :url => {:controller => 'tasks', + :action => 'add', + :project_id => @project, + :source_type => source_type, + :issue_id => @issue} if User.current.allowed_to?({:controller => 'tasks', + :action => 'add'}, @project) %> + +
+ + +

<%= l("label_#{source_type.name.underscore}_plural") %>

+ + <% unless !(@show_form == "true") %> + <% form_remote_tag( + :url => {:controller => 'tasks', + :action => 'add', + :issue_id => @issue, + :project_id => @project, + :source_type => source_type}, + :method => :post, + :html => {:id => "add-#{source_type.name.underscore}-form"}) do |f| %> +

<%= select_tag :source_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-#{source_type.name.underscore}-form"%>

+ <% end %> + <% end %> + + + + +
+ + + +<% end %> + + + diff --git a/app/views/layouts/contacts_base.html.erb b/app/views/layouts/contacts_base.html.erb new file mode 100644 index 0000000..823001f --- /dev/null +++ b/app/views/layouts/contacts_base.html.erb @@ -0,0 +1,50 @@ + + + + +<%=h html_title %> + + +<%= csrf_meta_tag %> +<%= favicon %> +<%= stylesheet_link_tag 'application', :media => 'all' %> +<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> +<%= javascript_heads %> +<%= heads_for_theme %> + +<%= call_hook :view_layouts_base_html_head %> + +<%= yield :header_tags -%> + + + + +<% content_for(:header_tags) do %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> + +<% content_for :sidebar do %> +

<%= l(:label_task_plural) %>

+ <%= link_to l(:label_contacts_view_all), { :controller => 'contacts', :action => 'index', :project_id => @project} %> + | + <%= link_to l(:label_deal_plural), { :controller => 'deals', :action => 'index', :project_id => @project} %> + +<% end %> + +
+ <%= render_flash_messages %> + <%= yield %> + <%= call_hook :view_layouts_base_content %> +
+
+ + + + + + diff --git a/app/views/mailer/issue_connected.html.erb b/app/views/mailer/issue_connected.html.erb new file mode 100644 index 0000000..aaaf00b --- /dev/null +++ b/app/views/mailer/issue_connected.html.erb @@ -0,0 +1,3 @@ +

<%=h @contact.project.name %>: <%= link_to(h(@contact.name), @contact_url) %>

+ +<%= l(:label_issue_added) %> <%= link_to(h(@issue.subject), @issue_url %> diff --git a/app/views/mailer/issue_connected.text.erb b/app/views/mailer/issue_connected.text.erb new file mode 100644 index 0000000..786e134 --- /dev/null +++ b/app/views/mailer/issue_connected.text.erb @@ -0,0 +1,3 @@ +<%=h @contact.project.name %>: <%= link_to(h(@contact.name), @contact_url) %> + +<%= l(:label_issue_added) %> <%= link_to(h(@issue.subject), @issue_url %> diff --git a/app/views/mailer/issue_connected.text.html.rhtml b/app/views/mailer/issue_connected.text.html.rhtml new file mode 100644 index 0000000..aaaf00b --- /dev/null +++ b/app/views/mailer/issue_connected.text.html.rhtml @@ -0,0 +1,3 @@ +

<%=h @contact.project.name %>: <%= link_to(h(@contact.name), @contact_url) %>

+ +<%= l(:label_issue_added) %> <%= link_to(h(@issue.subject), @issue_url %> diff --git a/app/views/mailer/issue_connected.text.plain.rhtml b/app/views/mailer/issue_connected.text.plain.rhtml new file mode 100644 index 0000000..786e134 --- /dev/null +++ b/app/views/mailer/issue_connected.text.plain.rhtml @@ -0,0 +1,3 @@ +<%=h @contact.project.name %>: <%= link_to(h(@contact.name), @contact_url) %> + +<%= l(:label_issue_added) %> <%= link_to(h(@issue.subject), @issue_url %> diff --git a/app/views/mailer/note_added.html.erb b/app/views/mailer/note_added.html.erb new file mode 100644 index 0000000..caf3213 --- /dev/null +++ b/app/views/mailer/note_added.html.erb @@ -0,0 +1,5 @@ +

<%=h @note.source.project.name %>: <%= link_to(h(@note.source.name), @note_url) %>

+ +<%=h @note.author %> + +<%= textilizable(@note, :content, :only_path => false) %> diff --git a/app/views/mailer/note_added.text.erb b/app/views/mailer/note_added.text.erb new file mode 100644 index 0000000..e975680 --- /dev/null +++ b/app/views/mailer/note_added.text.erb @@ -0,0 +1,5 @@ +<%= link_to( h(@note.source.name), @note_url) %> + +<%= @note.author %> + +<%= @note.content %> \ No newline at end of file diff --git a/app/views/mailer/note_added.text.html.rhtml b/app/views/mailer/note_added.text.html.rhtml new file mode 100644 index 0000000..caf3213 --- /dev/null +++ b/app/views/mailer/note_added.text.html.rhtml @@ -0,0 +1,5 @@ +

<%=h @note.source.project.name %>: <%= link_to(h(@note.source.name), @note_url) %>

+ +<%=h @note.author %> + +<%= textilizable(@note, :content, :only_path => false) %> diff --git a/app/views/mailer/note_added.text.plain.rhtml b/app/views/mailer/note_added.text.plain.rhtml new file mode 100644 index 0000000..e975680 --- /dev/null +++ b/app/views/mailer/note_added.text.plain.rhtml @@ -0,0 +1,5 @@ +<%= link_to( h(@note.source.name), @note_url) %> + +<%= @note.author %> + +<%= @note.content %> \ No newline at end of file diff --git a/app/views/my/blocks/_my_contacts.html.erb b/app/views/my/blocks/_my_contacts.html.erb new file mode 100644 index 0000000..422d772 --- /dev/null +++ b/app/views/my/blocks/_my_contacts.html.erb @@ -0,0 +1,27 @@ +

<%= l(:label_my_contact_plural) %>

+ +<% contacts = Contact.visible.find(:all, :conditions => {:assigned_to_id => User.current.id}, :limit => 20) %> + +
+ +
+ +<% if contacts.length > 0 %> +

<%= link_to l(:label_contact_view_all), + :controller => 'contacts', + :action => 'index', + :assigned_to_id => User.current.id %>

+<% end %> + +<% content_for(:header_tags) do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> \ No newline at end of file diff --git a/app/views/my/blocks/_my_contacts_avatars.html.erb b/app/views/my/blocks/_my_contacts_avatars.html.erb new file mode 100644 index 0000000..d427dac --- /dev/null +++ b/app/views/my/blocks/_my_contacts_avatars.html.erb @@ -0,0 +1,40 @@ +

<%= l(:label_my_contact_plural) %>

+ +<% contacts = Contact.visible.find(:all, :conditions => {:assigned_to_id => User.current.id}, :limit => 20) %> + +<% if contacts.length > 0 %> + + <% if contacts.select{|c| !c.is_company}.any? %> +
+ <% contacts.select{|c| !c.is_company}.each do |contact| %> +
+ <%= link_to avatar_to(contact, :size => "64"), contact_url(contact), :id => "avatar" %> + <%= render_contact_tooltip(contact, :icon => true) %> +
+ <% end %> +
+ <% end %> + + <% if contacts.select{|c| c.is_company}.any? %> +
+ <% contacts.select{|c| c.is_company}.each do |contact| %> +
+ <%= link_to avatar_to(contact, :size => "64"), contact_url(contact), :id => "avatar" %> + <%= render_contact_tooltip(contact, :icon => true) %> +
+ <% end %> +
+ <% end %> + + + +

<%= link_to l(:label_contact_view_all), + :controller => 'contacts', + :action => 'index', + :assigned_to_id => User.current.id %>

+<% end %> + +<% content_for(:header_tags) do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> \ No newline at end of file diff --git a/app/views/my/blocks/_my_contacts_stats.html.erb b/app/views/my/blocks/_my_contacts_stats.html.erb new file mode 100644 index 0000000..c6c4294 --- /dev/null +++ b/app/views/my/blocks/_my_contacts_stats.html.erb @@ -0,0 +1,39 @@ +

<%= l(:label_my_contacts_stats) %>

+ + +<% + from = Date.civil(Date.today.year, Date.today.month, 1) + to = (from >> 1) - 1 +%> + + + + + + + + + + + + <% Deal.find(:all, :select => "#{DealStatus.table_name}.name, #{Deal.table_name}.status_id, COUNT(DISTINCT #{Deal.table_name}.price) AS count, SUM(DISTINCT #{Deal.table_name}.price) AS total_sum", + :joins => "JOIN #{DealStatus.table_name} ON #{Deal.table_name}.status_id = #{DealStatus.table_name}.id", + :conditions => {:author_id => @user.id, :created_on => from..to}, + :group => "#{DealStatus.table_name}.name, #{DealStatus.table_name}.color, #{Deal.table_name}.status_id").each do |status| %> + + + + + <% end %> + +
<%= l(:label_contacts_created) %><%= Contact.count(:conditions => {:author_id => @user.id, :created_on => from..to}) %>
<%= l(:label_deals_created) %><%= Deal.count(:conditions => {:author_id => @user.id, :created_on => from..to}) %>
> + > + <%= h status.name %> + + <%= status.count %>
+ + +<% content_for(:header_tags) do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> \ No newline at end of file diff --git a/app/views/my/blocks/_my_deals.html.erb b/app/views/my/blocks/_my_deals.html.erb new file mode 100644 index 0000000..0c42da7 --- /dev/null +++ b/app/views/my/blocks/_my_deals.html.erb @@ -0,0 +1,32 @@ +

<%= l(:label_my_deal_plural) %>

+ +<% deals = Deal.visible.open.find(:all, :conditions => {:assigned_to_id => User.current.id}, :limit => 20) %> + +
+ +
+ +<% if deals.length > 0 %> +

<%= link_to l(:label_deal_view_all), + :controller => 'deals', + :action => 'index', + :assigned_to_id => User.current.id %>

+<% end %> + +<% content_for(:header_tags) do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> \ No newline at end of file diff --git a/app/views/notes/_add.html.erb b/app/views/notes/_add.html.erb new file mode 100644 index 0000000..eadd5d5 --- /dev/null +++ b/app/views/notes/_add.html.erb @@ -0,0 +1,24 @@ +
+

<%=l(:label_add_note_plural)%>

+ +
+ <% remote_form_for(:note, @note, :url => add_note_url(note_source, @project), :html => {:id => "add_note_form"}) do |f| %> + <%= render :partial => 'notes/form', :locals => {:f => f, :ajax_form => true} %> + <%= submit_tag l(:button_add_note) %> + <% end %> +
+ <%= link_to l(:label_note_show_extras), {}, :onclick => "Element.toggle('add_html_node'); Element.toggle('add_ajax_node'); $('add_html_node').down('.wiki-edit').value = $('add_ajax_node').down('.wiki-edit').value; return false;", :id => 'show_note_form_extras' %> +
+
+ + +
diff --git a/app/views/notes/_form.html.erb b/app/views/notes/_form.html.erb new file mode 100644 index 0000000..87bc822 --- /dev/null +++ b/app/views/notes/_form.html.erb @@ -0,0 +1,40 @@ +<%= error_messages_for 'note' %> + +<% if !ajax_form %> + +

+ <%= l(:field_type) %>: + <%= f.select :type_id, collection_for_note_types_select, { :include_blank => true } %> + + <%= l(:label_date) %>: + <%= f.text_field :created_on, :size => 15, :value => Date.current %><%= calendar_for "note_created_on" %> +

+ + <% @note.custom_field_values.each do |value| %> +

+ <%= custom_field_tag_with_label :note, value %> +

+ <% end -%> +
+
+<% end %> + + + +<%= f.text_area :content, :rows => 6, :class => 'wiki-edit' %> + +<% if !ajax_form %> + + + <%= file_field_tag 'note_attachments[1][file]', :size => 30, :id => nil %> + <%= text_field_tag 'attachments[1][description]', '', :size => 60, :id => nil %> + + +
+ <%= link_to l(:label_add_another_file), '#', :onclick => 'addNoteFileField(); return false;' %> + (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) + +
+
+
+<% end %> \ No newline at end of file diff --git a/app/views/notes/_last_notes.html.erb b/app/views/notes/_last_notes.html.erb new file mode 100644 index 0000000..7e7c954 --- /dev/null +++ b/app/views/notes/_last_notes.html.erb @@ -0,0 +1,8 @@ +<% if last_notes && last_notes.any? %> +

<%= l(:label_last_notes) %>

+
+ <% last_notes.each do |note| %> + <%= render :partial => 'notes/note_data', :locals => {:limit => 100}, :object => note %> + <% end %> +
+<% end %> diff --git a/app/views/notes/_note_data.html.erb b/app/views/notes/_note_data.html.erb new file mode 100644 index 0000000..fce33ef --- /dev/null +++ b/app/views/notes/_note_data.html.erb @@ -0,0 +1,19 @@ +<% limit = -1 if limit.blank? %> + + + + + +
<%= link_to avatar_to(note_data.source, :size => "32"), note_source_url(note_data.source), :id => "avatar" %> +

+ <%= note_type_icon(note_data) %> + <%= link_to_source note_data.source %>, + <%= time_tag(note_data.created_on) %> + <%= l(:label_time_ago) %> + <%= link_to('¶', {:controller => 'notes', :action => 'show', :project_id => @project, :note_id => note_data}, :title => l(:button_show), :class => "wiki-anchor") %> +

+
+ <%= textilizable(truncate(note_data.content, :length => limit)) %> +

<%= "#{l(:label_file_plural)}: #{note_data.attachments.collect{|a| a.filename}.join(', ')}" if note_data.attachments.any? %>

+
+
\ No newline at end of file diff --git a/app/views/notes/_note_header.html.erb b/app/views/notes/_note_header.html.erb new file mode 100644 index 0000000..e2586f6 --- /dev/null +++ b/app/views/notes/_note_header.html.erb @@ -0,0 +1,15 @@ +
+ + + + + + +
<%= link_to avatar_to(note_header.source, :size => "32"), note_source_url(note_header.source), :id => "avatar" %> +

+ <%= note_type_icon(note_header) %> + <%= l(:label_note_for) %> <%= note_header.source.name %> +

+

<%= authoring note_header.created_on, note_header.author %>

+
+
\ No newline at end of file diff --git a/app/views/notes/_note_item.html.erb b/app/views/notes/_note_item.html.erb new file mode 100644 index 0000000..320fb6a --- /dev/null +++ b/app/views/notes/_note_item.html.erb @@ -0,0 +1,42 @@ +<% show_info = true if show_info.nil? %> +
> + + + + <% if show_info %> + + <% end %> + + +
<%= link_to avatar_to(note_item.source, :size => "32"), note_source_url(note_item.source), :id => "avatar" %> +
+ <%= link_to(image_tag('edit.png'), {:controller => 'notes', :action => 'edit', :project_id => @project, :note_id => note_item}, :class => "delete", :title => l(:button_edit)) if note_item.editable_by?(User.current, @project) %> + + <%= link_to_remote(image_tag('delete.png'), + :url => {:controller => :notes, :action => 'destroy', :note_id => note_item, :project_id => @project}, + :method => :delete, + :confirm => l(:text_are_you_sure), + :html => {:class => "delete", :title => l(:button_delete) }) if note_item.destroyable_by?(User.current, @project) %> +
+

+ <%= note_type_icon(note_item) %> + <%= "#{note_item.subject} - " unless note_item.subject.blank? %> + <%= link_to_source(note_item.source) + "," if show_info %> + <%= authoring note_item.created_on, note_item.author %> + <%= link_to('¶', {:controller => 'notes', :action => 'show', :project_id => @project, :note_id => note_item}, :title => l(:button_show), :class => "wiki-anchor") %> +

+
+ <% note_item.custom_values.each do |custom_value| %> + <% if !custom_value.value.blank? %> +

<%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %>

+ <% end %> + <% end %> +
+
+ <%= note_content(note_item) %> + <%= auto_thumbnails(note_item) %> + <%= render :partial => 'attachments/links', :locals => {:attachments => note_item.attachments, :options => {}} if note_item.attachments.any? %> +
+
+ +
\ No newline at end of file diff --git a/app/views/notes/_notes_list.html.erb b/app/views/notes/_notes_list.html.erb new file mode 100644 index 0000000..cf7037c --- /dev/null +++ b/app/views/notes/_notes_list.html.erb @@ -0,0 +1,8 @@ +<% unless @notes.empty? %> + <% @notes.each do |note| %> + <%= render :partial => 'notes/note_data', :locals => {:limit => 1000}, :object => note %> + <% end %> +

<%= link_to l(:label_previous), { :page => @notes_pages.current.previous, :params => {:project_id => params[:project_id], :tag => params[:tag]}} if @notes_pages.current.previous %> <%= pagination_links @notes_pages, :params => {:project_id => params[:project_id], :tag => params[:tag]}%> <%= link_to l(:label_next), { :page => @notes_pages.current.next, :params => {:project_id => params[:project_id], :tag => params[:tag]} } if @notes_pages.current.next %>

+<% else %> +

<%=l(:label_no_data)%>

+<% end %> diff --git a/app/views/notes/edit.html.erb b/app/views/notes/edit.html.erb new file mode 100644 index 0000000..e80b0d0 --- /dev/null +++ b/app/views/notes/edit.html.erb @@ -0,0 +1,63 @@ +<%= breadcrumb link_to(@note.source.name, note_source_url(@note.source)) %> + +<%= render :partial => 'note_header', :object => @note %> + +
+ +<% form_for :note, @note, :url => {:controller => "notes", :action => 'update', :project_id => @project, :note_id => @note}, :html => { :multipart => true} do |f| %> + + + + + + +
+
<%= f.select :type_id, collection_for_note_types_select, { :include_blank => true } %> +
+
<%= f.text_field :created_on, :size => 15 %><%= calendar_for "note_created_on" %> +
+ + + + +<% @note.custom_field_values.each do |value| %> +

+ <%= custom_field_tag_with_label :note, value %> +

+<% end -%> +
+ +

<%= f.text_area :content , :cols => 80, :rows => 8, :class => 'wiki-edit', :label=>l(:field_contact_background) %> +<%= wikitoolbar_for 'note_content' %>

+<%= link_to_attachments @note, :author => false %> +


+ + <%= file_field_tag 'note_attachments[1][file]', :size => 30, :id => nil %> + <%= text_field_tag 'attachments[1][description]', '', :size => 60, :id => nil %> + + +
+ <%= link_to l(:label_add_another_file), '#', :onclick => 'addNoteFileField(); return false;' %> + (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>) +

+ +<%= submit_tag l(:button_save) -%> +<% end -%> +
+ +<% html_title "#{l(:label_note_for)} #{@note.source.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 %> diff --git a/app/views/notes/new.html.erb b/app/views/notes/new.html.erb new file mode 100644 index 0000000..79a3522 --- /dev/null +++ b/app/views/notes/new.html.erb @@ -0,0 +1 @@ +

Notes#new

diff --git a/app/views/notes/show.html.erb b/app/views/notes/show.html.erb new file mode 100644 index 0000000..097d14d --- /dev/null +++ b/app/views/notes/show.html.erb @@ -0,0 +1,36 @@ +
+<%= link_to_if_authorized(l(:button_edit), {:controller => 'notes', :action => 'edit', :project_id => @project, :note_id => @note}, :class => 'icon icon-edit', :title => l(:button_edit)) %> +
+ + +<%= breadcrumb link_to(@note.source.name, note_source_url(@note.source)) %> + +<%= render :partial => 'note_header', :object => @note %> +<% @note.custom_values.each do |custom_value| %> + <% if !custom_value.value.blank? %> +

<%= custom_value.custom_field.name%>: <%=h show_value(custom_value) %>

+ <% end %> +<% end %> + +
+ <%= textilizable(@note, :content) %> + <%= auto_thumbnails(@note) %> +
+ +<%= link_to_attachments @note, :author => false %> + + +<% html_title "#{l(:label_note_for)} #{@note.source.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 %> \ No newline at end of file diff --git a/app/views/projects/_contacts.html.erb b/app/views/projects/_contacts.html.erb new file mode 100644 index 0000000..2ead199 --- /dev/null +++ b/app/views/projects/_contacts.html.erb @@ -0,0 +1,45 @@ +<% if ContactsSetting[:contacts_show_on_projects_show, @project.id].to_i > 0 %> + +<% contacts = @project.contacts.visible.find(:all, :limit => 50, :order => "#{Contact.table_name}.created_on DESC") %> + +<% if contacts.length > 0 %> +
+

<%= l(:label_contact_plural) %>

+ + <% if contacts.select{|c| !c.is_company}.any? %> +
+ <% contacts.select{|c| !c.is_company}.each do |contact| %> +
+ <%= link_to avatar_to(contact, :size => "64"), contact_url(contact), :id => "avatar" %> + <%= render_contact_tooltip(contact, :icon => true) %> +
+ <% end %> +
+ <% end %> + + <% if contacts.select{|c| c.is_company}.any? %> +
+ <% contacts.select{|c| c.is_company}.each do |contact| %> +
+ <%= link_to avatar_to(contact, :size => "64"), contact_url(contact), :id => "avatar" %> + <%= render_contact_tooltip(contact, :icon => true) %> +
+ <% end %> +
+ <% end %> + + + +

<%= link_to l(:label_contact_view_all), + :controller => 'contacts', + :action => 'index', + :project_id => project.id %>

+
+<% end %> + +<% content_for(:header_tags) do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> + +<% end %> \ No newline at end of file diff --git a/app/views/projects/_contacts_settings.html.erb b/app/views/projects/_contacts_settings.html.erb new file mode 100644 index 0000000..14ef552 --- /dev/null +++ b/app/views/projects/_contacts_settings.html.erb @@ -0,0 +1,22 @@ +<% form_tag({:controller => "contacts_settings", :action => "save", :project_id => @project}, :method => :post, :class => "tabular") do %> +
+ +

+ + <%= hidden_field_tag('contacts_settings[contacts_show_deals_tab]', 0) %> + <%= check_box_tag 'contacts_settings[contacts_show_deals_tab]', 1, ContactsSetting[:contacts_show_deals_tab, @project.id].to_i > 0 %> +

+ +

+ + <%= hidden_field_tag('contacts_settings[contacts_show_on_projects_show]', 0) %> + <%= check_box_tag 'contacts_settings[contacts_show_on_projects_show]', 1, ContactsSetting[:contacts_show_on_projects_show, @project.id].to_i > 0 %> +

+ + <%= call_hook(:view_contacts_project_settings_bottom, :project => @project) %> + +
+ <%= call_hook(:view_contacts_project_settings_top, :project => @project) %> + <%= submit_tag l(:button_save) %> +<% end %> + diff --git a/app/views/projects/_deal_statuses.html.erb b/app/views/projects/_deal_statuses.html.erb new file mode 100644 index 0000000..adcdc28 --- /dev/null +++ b/app/views/projects/_deal_statuses.html.erb @@ -0,0 +1,28 @@ +

<%= l(:label_deal_status_plural) %>

+ +<% if DealStatus.all.any? %> + <% form_tag({:controller => "deal_statuses", :action => "assing_to_project", :project_id => @project}, :method => :put, :class => "tabular") do %> + + + + + + + + + <% DealStatus.all.each do |status| %> + + + + + + + + <% end %> + +
<%= l(:field_name) %><%=l(:field_is_default)%><%=l(:field_deal_status_is_closed)%><%= l(:field_active) %>
     <%= h(status.name) %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %> + <%= check_box_tag "deal_statuses[]", status.id , @project.deal_statuses.include?(status) %> +
+ <%= submit_tag l(:button_save) %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/projects/_deals_settings.html.erb b/app/views/projects/_deals_settings.html.erb new file mode 100644 index 0000000..ba9dc83 --- /dev/null +++ b/app/views/projects/_deals_settings.html.erb @@ -0,0 +1,28 @@ +

<%= l(:label_deal_category_plural) %>

+ +<% if @project.deal_categories.any? %> + + + + + + +<% for category in @project.deal_categories %> + <% unless category.new_record? %> + + + + + <% end %> +<% end %> + +
<%= l(:field_name) %>
<%= link_to_if_authorized h(category.name), { :controller => 'deal_categories', :action => 'edit', :id => category } %> + <%= link_to_if_authorized l(:button_delete), {:controller => 'deal_categories', :action => 'destroy', :id => category}, :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon icon-del' %> +
+<% else %> +

<%= l(:label_no_data) %>

+<% end %> + +

<%= link_to_if_authorized l(:label_deal_category_new), :controller => 'deal_categories', :action => 'new', :project_id => @project %>

+ +<%= render :partial => "projects/deal_statuses" %> \ No newline at end of file diff --git a/app/views/projects/settings.rhtml b/app/views/projects/settings.rhtml new file mode 100644 index 0000000..0f055b6 --- /dev/null +++ b/app/views/projects/settings.rhtml @@ -0,0 +1,23 @@ +

<%=l(:label_settings)%>

+ +<% +tabs = project_settings_tabs +if @project.module_enabled?(:contacts_module) + if User.current.allowed_to?(:manage_contacts, @project) + tabs.push({ :name => 'contacts', + :action => :manage_contacts, + :partial => 'projects/contacts_settings', + :label => :label_contact_plural }) + tabs.push({ :name => 'deals', + :action => :manage_contacts, + :partial => 'projects/deals_settings', + :label => :label_deal_plural }) + + end + + call_hook(:add_contacts_project_settings_tab, {:project => @project, :tabs => tabs}) +end +%> +<%= render_tabs tabs %> + +<% html_title(l(:label_settings)) -%> diff --git a/app/views/sale_funel/_sale_funel.html.erb b/app/views/sale_funel/_sale_funel.html.erb new file mode 100644 index 0000000..811a327 --- /dev/null +++ b/app/views/sale_funel/_sale_funel.html.erb @@ -0,0 +1,29 @@ +<% if @sale_funel.any? %> + + + + + + + <% @sale_funel.each do |deal_status, deals_count, deals_sum| %> + + + + + + <% end %> +
<%= h l(:label_deal_status) %><%= h l(:label_count) %><%= h l(:label_total) %>
> + + <%= h "#{deal_status.name}" %> + + + + <%= h deals_count %> + + + + <%= deals_sum_to_currency deals_sum %> + + +
+<% end %> \ No newline at end of file diff --git a/app/views/sale_funel/index.html.erb b/app/views/sale_funel/index.html.erb new file mode 100644 index 0000000..6eb4fb6 --- /dev/null +++ b/app/views/sale_funel/index.html.erb @@ -0,0 +1,62 @@ +

<%= h l(:label_sale_funel) %>

+ +
+ <% form_tag(params, :id => "query_form") do %> + <%= hidden_field_tag('project_id', @project.to_param) if @project %> + <% no_filters = (params[:status_id].blank? && params[:assigned_to_id].blank?) %> + +
+ <%= l(:label_filter_plural) %> +
+

+ <%= label_tag l(:field_deal_status_is_closed) + " "%> + <%= check_box_tag 'is_closed', "1", params[:is_closed] %> + + <%= label_tag l(:label_period) + " "%> + <%= select_tag 'period', options_for_period_select(params[:period]) %> + + <% if @project && !@project.deal_categories.empty? %> + <%= label_tag l(:label_deal_category) + " "%> + <%= select_tag 'category_id', options_for_select(@project.deal_categories.collect {|c| [c.name, c.id]}.insert(0, [""]), params[:category_id]) %> + <% end %> + + <%= label_tag l(:label_assigned_to) + " " %> + <%= select_tag :author_id, options_for_select(Deal.available_users(@project).collect{|u| [u.name, u.id]}.insert(0, [""]), params[:author_id]) %> +

+ +
+
+

+ + <%= link_to_remote l(:button_apply), + { :url => {}, + :update => "sale_funel", + :with => "Form.serialize('query_form')" + }, :class => 'icon icon-checked' %> + + <%= link_to l(:button_clear), + {:project_id => @project }, + :method => :get, + :update => "sale_funel", + :class => 'icon icon-reload' %> +

+ <% end %> + +
+ +
+ <%= render :partial => 'sale_funel' %> +
+ +<% 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 => 'deals/deals_statistics' %> + <%= render :partial => 'common/recently_viewed' %> + +<% end %> \ No newline at end of file diff --git a/app/views/settings/_contacts.html.erb b/app/views/settings/_contacts.html.erb new file mode 100644 index 0000000..a3220e9 --- /dev/null +++ b/app/views/settings/_contacts.html.erb @@ -0,0 +1,8 @@ +<% contacts_tabs = [{:name => 'general', :partial => 'settings/contacts_general', :label => :label_general}, + {:name => 'tags', :partial => 'settings/contacts_tags', :label => :label_tags_plural}, + {:name => 'deal_statuses', :partial => 'settings/contacts_deal_statuses', :label => :label_deal_status_plural} + ] %> + +<%= render_tabs contacts_tabs %> + +<% html_title(l(:label_settings), l(:label_contact_plural)) -%> diff --git a/app/views/settings/_contacts_deal_statuses.html.erb b/app/views/settings/_contacts_deal_statuses.html.erb new file mode 100644 index 0000000..3e27b23 --- /dev/null +++ b/app/views/settings/_contacts_deal_statuses.html.erb @@ -0,0 +1,34 @@ +
+<%= link_to l(:label_deal_status_new), {:controller => "deal_statuses", :action => 'new'}, :class => 'icon icon-add' %> +
+ +

<%=l(:label_deal_status_plural)%>

+ + + + + + + + + + +<% for status in DealStatus.all( :order => "position") %> + "> + + + + + + +<% end %> + +
<%=l(:field_status)%><%=l(:field_is_default)%><%=l(:field_deal_status_is_closed)%><%=l(:button_sort)%>
     <%= link_to status.name, :controller => "deal_statuses", :action => 'edit', :id => status %><%= checked_image status.is_default? %><%= checked_image status.is_closed? %><%= reorder_links('deal_status', {:controller => "deal_statuses", :action => 'update', :id => status}) %> + <%= link_to(l(:button_delete), {:controller => "deal_statuses", :action => 'destroy', :id => status }, + :method => :post, + :confirm => l(:text_are_you_sure), + :class => 'icon icon-del') %> +
+ + +<% html_title(l(:label_deal_status_plural)) -%> diff --git a/app/views/settings/_contacts_general.html.erb b/app/views/settings/_contacts_general.html.erb new file mode 100644 index 0000000..0239f96 --- /dev/null +++ b/app/views/settings/_contacts_general.html.erb @@ -0,0 +1,21 @@ +

+ + <%= check_box_tag 'settings[use_gravatar]', 1, @settings[:use_gravatar] %> +

+ +

+ + <%= select_tag 'settings[name_format]', options_for_select(Contact::CONTACT_FORMATS.keys.collect {|f| [Contact.new(:first_name => 'Firstname', :last_name => 'Lastname', :middle_name => 'Middlename').name(f), f.to_s] }, @settings[:name_format] ) %> +

+ +

+ + <%= check_box_tag 'settings[auto_thumbnails]', 1, @settings[:auto_thumbnails] %> +

+ +<% if !Object.const_defined?(:Magick) %> +

+ + <%= text_field_tag 'settings[max_thumbnail_file_size]', @settings[:max_thumbnail_file_size], :size => 6 %> <%= l(:"number.human.storage_units.units.kb") %> +

+<% end %> \ No newline at end of file diff --git a/app/views/settings/_contacts_tags.html.erb b/app/views/settings/_contacts_tags.html.erb new file mode 100644 index 0000000..21e8410 --- /dev/null +++ b/app/views/settings/_contacts_tags.html.erb @@ -0,0 +1,24 @@ +

<%= l(:label_tags_plural) %>

+ + + + + + + + <% ActsAsTaggableOn::Tag.find(:all, :include => :taggings, :conditions => "#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = 'Contact'", :order => :name).each do |tag| %> + hascontextmenu "> + + + + + + <% end %> + +
<%= l(:field_name) %>
     <%= link_to tag.name, :controller => "contacts_tags", :action => 'edit', :id => tag %> + + <%= link_to l(:button_delete), {:controller => "contacts_tags", :action => 'destroy', :id => tag }, + :method => :post, + :confirm => l(:text_are_you_sure), + :class => 'icon icon-del' %> +
diff --git a/app/views/users/_contact.html.erb b/app/views/users/_contact.html.erb new file mode 100644 index 0000000..dbc299a --- /dev/null +++ b/app/views/users/_contact.html.erb @@ -0,0 +1,49 @@ +<% @contact = Contact.visible.find(:first, :conditions => ["#{Contact.table_name}.email LIKE ?", "%#{@user.mail}%" ]) %> + +

<%= l(:label_contact) %>

+<% unless @contact.blank? || !@contact.visible? %> +
+ + + + + <% if @contact.phones.any? || @contact.emails.any? %> + + <% end %> + + +
<%= link_to avatar_to(@contact, :size => "50"), contact_url(@contact), :id => "avatar", :title => @contact.name %> +

<%= h @contact.name %>

+

+ <%= h @contact.job_title %> + <% if !@contact.is_company %> + <%= " #{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 %> + <% end %> +

+ + <%= render :partial => 'contacts/form_tags' %> + +
+
    + <% if @contact.phones.any? %> +
  • <%= @contact.phones.first %>
  • + <% end %> + + <% if @contact.emails.any? %> + + <% end %> +
+
+
+<% end %> + + +<% content_for :header_tags do %> + <%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %> + <%= stylesheet_link_tag :contacts, :plugin => 'redmine_contacts' %> +<% end %> \ No newline at end of file diff --git a/assets/images/calendar_view_day.png b/assets/images/calendar_view_day.png new file mode 100644 index 0000000..9740f76 Binary files /dev/null and b/assets/images/calendar_view_day.png differ diff --git a/assets/images/case.png b/assets/images/case.png new file mode 100644 index 0000000..aad3c20 Binary files /dev/null and b/assets/images/case.png differ diff --git a/assets/images/company.png b/assets/images/company.png new file mode 100644 index 0000000..b05bc75 Binary files /dev/null and b/assets/images/company.png differ diff --git a/assets/images/date.png b/assets/images/date.png new file mode 100644 index 0000000..783c833 Binary files /dev/null and b/assets/images/date.png differ diff --git a/assets/images/deal.png b/assets/images/deal.png new file mode 100644 index 0000000..5c47353 Binary files /dev/null and b/assets/images/deal.png differ diff --git a/assets/images/email.png b/assets/images/email.png new file mode 100644 index 0000000..7348aed Binary files /dev/null and b/assets/images/email.png differ diff --git a/assets/images/feed.png b/assets/images/feed.png new file mode 100644 index 0000000..23160c3 Binary files /dev/null and b/assets/images/feed.png differ diff --git a/assets/images/money.png b/assets/images/money.png new file mode 100644 index 0000000..42c52d0 Binary files /dev/null and b/assets/images/money.png differ diff --git a/assets/images/money_dollar.png b/assets/images/money_dollar.png new file mode 100644 index 0000000..59af163 Binary files /dev/null and b/assets/images/money_dollar.png differ diff --git a/assets/images/person.png b/assets/images/person.png new file mode 100644 index 0000000..7160a73 Binary files /dev/null and b/assets/images/person.png differ diff --git a/assets/images/phone.png b/assets/images/phone.png new file mode 100644 index 0000000..c39f162 Binary files /dev/null and b/assets/images/phone.png differ diff --git a/assets/images/telephone.png b/assets/images/telephone.png new file mode 100644 index 0000000..cecc436 Binary files /dev/null and b/assets/images/telephone.png differ diff --git a/assets/images/user_suit.png b/assets/images/user_suit.png new file mode 100644 index 0000000..b3454e1 Binary files /dev/null and b/assets/images/user_suit.png differ diff --git a/assets/images/vcard.png b/assets/images/vcard.png new file mode 100644 index 0000000..c02f315 Binary files /dev/null and b/assets/images/vcard.png differ diff --git a/assets/javascripts/contacts.js b/assets/javascripts/contacts.js new file mode 100644 index 0000000..3c0bcef --- /dev/null +++ b/assets/javascripts/contacts.js @@ -0,0 +1,325 @@ +// Some parts of code based on original part of the code from redmine_tags of Aleksey V Zapparov +// and provided exclusively to Kirill Bezrukov under terms of MIT license. +// +// Copyright (c) 2010-2011 Aleksey V Zapparov +// Copyright (c) 2011 Kirill Bezrukov + + +var phoneFieldCount = 1; +function addPhoneField() { + if (phoneFieldCount >= 5) return false + phoneFieldCount++; + + var l = document.createElement("label"); + l.appendChild(document.createTextNode("")); + + var d = document.createElement("input"); + d.name = "contact[phones][]"; + d.type = "text"; + d.size = "30"; + + p = document.getElementById("phones_fields"); + + k = p.insertBefore(document.createElement("p")); + k.appendChild(l); + k.appendChild(d); + +} + + +var noteFileFieldCount = 1; + +function addNoteFileField() { + if (noteFileFieldCount >= 10) return false + noteFileFieldCount++; + var f = document.createElement("input"); + f.type = "file"; + f.name = "note_attachments[" + noteFileFieldCount + "][file]"; + f.size = 30; + var d = document.createElement("input"); + d.type = "text"; + d.name = "note_attachments[" + noteFileFieldCount + "][description]"; + d.size = 60; + + p = document.getElementById("note_attachments_fields"); + p.appendChild(document.createElement("br")); + p.appendChild(f); + p.appendChild(d); +} + +function removeField(link) { + Effect.Fade($(link).up(),{duration:0.5}); + $(link).previous().value = ''; +} + +function addField(link, content) { + $(link).up().insert({ + before: content + }) +} + +var Redmine = Redmine || {}; + +Redmine.TagsInput = Class.create({ + initialize: function(element) { + this.element = $(element); + this.input = new Element('input', { 'type': 'text', 'autocomplete': 'off', 'size': 20 }); + this.button = new Element('span', { 'class': 'tag-add icon icon-add' }); + this.tags = new Hash(); + + Event.observe(this.button, 'click', this.readTags.bind(this)); + Event.observe(this.input, 'keypress', this.onKeyPress.bindAsEventListener(this)); + + this.element.insert({ 'after': this.input }); + this.input.insert({ 'after': this.button }); + this.addTagsList(this.element.value); + }, + + readTags: function() { + this.addTagsList(this.input.value); + this.input.value = ''; + }, + + onKeyPress: function(event) { + if (Event.KEY_RETURN == event.keyCode) { + this.readTags(event); + Event.stop(event); + } + }, + + addTag: function(tag) { + if (tag.blank() || this.tags.get(tag)) return; + + var button = new Element('span', { 'class': 'tag-delete icon icon-del' }); + var label = new Element('span', { 'class': 'tag-label' }).insert(tag).insert(button); + + this.tags.set(tag, 1); + this.element.value = this.getTagsList(); + this.element.insert({ 'before': label }); + + Event.observe(button, 'click', function(){ + this.tags.unset(tag); + this.element.value = this.getTagsList(); + label.remove(); + }.bind(this)); + }, + + addTagsList: function(tags_list) { + var tags = tags_list.split(','); + for (var i = 0; i < tags.length; i++) { + this.addTag(tags[i].strip()); + } + }, + + getTagsList: function() { + return this.tags.keys().join(','); + }, + + autocomplete: function(container, url) { + new Ajax.Autocompleter(this.input, container, url, { + 'minChars': 1, + 'frequency': 0.5, + 'paramName': 'q', + 'updateElement': function(el) { + this.input.value = el.getAttribute('name'); + this.readTags(); + }.bind(this) + }); + } +}); + +function observeContactTagsField(url) { + new Redmine.TagsInput('contact_tag_list').autocomplete('contact_tag_candidates', url); +} + +function observeTagsField(url, tag_list, tag_candidates) { + new Redmine.TagsInput(tag_list).autocomplete(tag_candidates, url); +} + +// +// CheckContactsAndDeals +// + +function checkAllContacts(field) +{ + for (i = 0; i < field.length; i++) { + field[i].checked = true; + Element.up(field[i], 'tr').addClassName('context-menu-selection'); + } +} + +function uncheckAllContacts(field) +{ + for (i = 0; i < field.length; i++) { + field[i].checked = false; + Element.up(field[i], 'tr').removeClassName('context-menu-selection'); + } +} + + +function toggleContact(event, element) +{ + if (event.shiftKey==1) + { + if (element.checked) { + checkAllContacts($$('.contacts.index td.checkbox input')); + } + else + { + uncheckAllContacts($$('.contacts.index td.checkbox input')); + } + } + else + { + Element.up(element, 'tr').toggleClassName('context-menu-selection'); + } +} + + + + +/** + * basic color picker + * + * + * $$('.colorpicker').each(function(el){var p = new ColorPicker(el)}); + */ +var ColorPicker = Class.create({ + colors: [], + element: null, + + initialize: function(element, trigger) { + this.colourArray = new Array(); + this.element = $(element); + + this.buildTable(); + + this.element.observe('click', this.togglePicker.bindAsEventListener(this)); + this.element.setStyle({backgroundColor: this.element.value}); + }, + + buildTable: function(){ + this.initColors(); + var table = new Element('table'); + + for( var i = 0, len = this.colors.length; i < len; i++ ){ + var color = this.colors[i]; + + if( i % 8 == 0 ){ + row = new Element('tr'); + table.insert( row ); + } + + row.insert( '' + + color + + '' ); + } + + table.setStyle('top:0;width:15em;border: 1px solid #D7D7D7;'); + + var holder = new Element( 'div', {style:'position:relative;'}) + this.element.up().insert(holder.insert(table.hide())); + + table.select('a').invoke('observe', 'click', this.onClick.bindAsEventListener(this)); + }, + + onClick: function( evt ){ + var color = '#' + evt.element().href.split('#').pop(); + this.element.value = color; + this.element.setStyle({backgroundColor: color}); + evt.findElement('table').hide(); + evt.stop(); + }, + + togglePicker: function( evt ){ + evt.element().up().down('table').show(); + + }, + + initColors: function() { + var colourMap = new Array('00', '33', '66', '99', 'AA', 'CC', 'EE', 'FF'); + for(i = 0; i < colourMap.length; i++) { + this.colors.push(colourMap[i] + colourMap[i] + colourMap[i]); + } + + // Blue + for(i = 1; i < colourMap.length; i++) { + if(i != 0 && i != 4 && i != 6) { + this.colors.push(colourMap[0] + colourMap[0] + colourMap[i]); + } + } + for(i = 1; i < colourMap.length; i++) { + if(i != 2 && i != 4 && i != 6 && i != 7) { + this.colors.push(colourMap[i] + colourMap[i] + colourMap[7]); + } + } + + // Green + for(i = 1; i < colourMap.length; i++) { + if(i != 0 && i != 4 && i != 6) { + this.colors.push(colourMap[0] + colourMap[i] + colourMap[0]); + } + } + for(i = 1; i < colourMap.length; i++) { + if(i != 2 && i != 4 && i != 6 && i != 7) { + this.colors.push(colourMap[i] + colourMap[7] + colourMap[i]); + } + } + + // Red + for(i = 1; i < colourMap.length; i++) { + if(i != 0 && i != 4 && i != 6) { + this.colors.push(colourMap[i] + colourMap[0] + colourMap[0]); + } + } + for(i = 1; i < colourMap.length; i++) { + if(i != 2 && i != 4 && i != 6 && i != 7) { + this.colors.push(colourMap[7] + colourMap[i] + colourMap[i]); + } + } + + // Yellow + for(i = 1; i < colourMap.length; i++) { + if(i != 0 && i != 4 && i != 6) { + this.colors.push(colourMap[i] + colourMap[i] + colourMap[0]); + } + } + for(i = 1; i < colourMap.length; i++) { + if(i != 2 && i != 4 && i != 6 && i != 7) { + this.colors.push(colourMap[7] + colourMap[7] + colourMap[i]); + } + } + + // Cyan + for(i = 1; i < colourMap.length; i++) { + if(i != 0 && i != 4 && i != 6) { + this.colors.push(colourMap[0] + colourMap[i] + colourMap[i]); + } + } + for(i = 1; i < colourMap.length; i++) { + if(i != 2 && i != 4 && i != 6 && i != 7) { + this.colors.push(colourMap[i] + colourMap[7] + colourMap[7]); + } + } + + // Magenta + for(i = 1; i < colourMap.length; i++) { + if(i != 0 && i != 4 && i != 6) { + this.colors.push(colourMap[i] + colourMap[0] + colourMap[i]); + } + } + for(i = 1; i < colourMap.length; i++) { + if(i != 2 && i != 4 && i != 6 && i != 7) { + this.colors.push(colourMap[7] + colourMap[i] + colourMap[i]); + } + } + } +}); \ No newline at end of file diff --git a/assets/stylesheets/contacts.css b/assets/stylesheets/contacts.css new file mode 100644 index 0000000..aaab715 --- /dev/null +++ b/assets/stylesheets/contacts.css @@ -0,0 +1,416 @@ +div.contact {background:#f4e9f2; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;} +div.deal {background:#edfff2; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;} +form#add_task_form {background:#ffffff; display: block; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7; width: 92%;} + +/********************/ +/* CONTACT ISSUES */ +/********************/ + +div#contact_issues { + margin-right: 4px; +} + +div#contact_issues td.done_checkbox { + width: 10px; + vertical-align: top; + padding-top: 4px; +} + +div#contact_issues td.issue_subject { +vertical-align: top; +width: 100%; +} + + +/********************/ +/* ADD NOTE FORM */ +/********************/ + +div.add-note {margin-left: 5px; margin-right: 6px;} +div.add-note textarea.wiki-edit {margin-bottom: 10px; width: 100%;} +div.add-note p {margin-bottom: 0px; margin-top: 5px;} +.note-custom-fields label {margin-right: 10px;} + +/*div#contact_list table.index tbody tr:hover { background-color:#ffffdd; } */ + +div#sidebar div.contextual { + margin-right: 8px; +} + + +/* note-data*/ + +span.note-info { + font-size: 11px; + margin: 0px; + padding: 0px; + line-height: 1.5; + color: rgb(119, 119, 119); +} + + +table.subject_header { + width: 100%; +} + +table.subject_header td.avatar { + vertical-align: top; + vertical-align: top; + text-align: right; + width: 57px; +} + +table.subject_header td.subject_info { + padding-left: 15px; + padding-right: 15px; + border-left: 1px solid #D7D7D7; + min-width: 200px; + width: 200px; +} + +table.subject_header td.subject_info ul { + list-style: none; + padding-left: 0px; +} + +table.subject_header td.subject_info li { + line-height: 20px; + white-space: nowrap; +} + +.icon-phone { background-image: url(../images/telephone.png); } +.icon-email { background-image: url(../images/email.png); } + + +h4.contacts_header {border-bottom: 0px;} + + + +/********************/ +/* Search */ +/********************/ +h2.contacts_header {border-bottom: 0px;} + + +div.filters h2 {margin-bottom: 5px;} +div.filters h2 .scope_title a { color: #444; text-decoration: none; } +div.filters h2 .scope_title a:hover { text-decoration: none; display: inline; color: #aaa;} + +div.filters .tags span.tag a { line-height: 0; } + +div.search_and_filters { margin-bottom: 10px; } + +div.live_search { font-size: 16px; } + +div.filters .live_search {position:relative;} + +.live_search input.live_search_field, label#search_overlabel { font-size: 16px; } + +label#search_overlabel { + color: #D7D7D7 !important; + position: absolute; + font-weight: normal; + padding-left: 8px; + top: 5px; + white-space: nowrap; + cursor: text; +} + +/**************************************************************/ +/* TAGS */ +/**************************************************************/ + + +.tags { + margin-top: 5px; +} + +.tags a img { white-space: nowrap; vertical-align: bottom;} + +span.tag a { + background-color: grey; + color: white; + padding: 3px 4px; + font-size: 10px; + /* display: block;*/ + white-space: nowrap; + line-height: 20px; +} + +span.tag { + display: inline-block; + } + +span.tag a:hover { + background-color: silver; + text-decoration: none; + color: black; +} + +span.tag a.selected:hover { + background-color: #eee; +} + +span.tag a.selected { + background-color: #eee; + color: black; +} + +span.simple_tag a.selected { + color: #999; +} + +#edit_tags_form.box { + margin: 1px 5px 0px 0px; +} + +#edit_tags_form.box label { + margin-right: 5px; + font-weight: bold; +} + +#edit_tags_form.box #contact_tags { + margin-bottom: 10px; +} + +div#tags_data span.contextual {float: none; padding-left: 0px;} + + +h2 span.tag a { + font-size: inherit; + /* display: block;*/ +} + +#responsible_user ul {margin: 0; padding: 0;} +#responsible_user li {list-style-type:none; margin: 3px 2px 0px 0px; padding: 0px 0px 0px 0px;} +#responsible_user select {width: 95%; display: block;} +#responsible_user a.delete {opacity: 0.4;} +#responsible_user a.delete:hover {opacity: 1;} +#responsible_user img.gravatar {vertical-align: middle;margin: 0 4px 2px 0;} + + +/**************************************************************/ +/* CONTACTS_DEALS_LIST */ +/**************************************************************/ + + +table.contacts.index { + border-top: 1px solid rgb(239, 239, 239); + border-right: 1px solid rgb(239, 239, 239); + border-left: 1px solid rgb(239, 239, 239); + width: 100%; + border-spacing: 0px 0px; + margin-bottom: 4px; +} + +table.contacts.index tr.selected{ background-color: #507AAA !important; color: #F8F8F8 !important;} +/*table.contacts.index tr.selected a { color: #F8F8F8 !important; } */ +table.contacts.index tr.context-menu-selection h2 { color: #F8F8F8 !important; } +table.contacts.index tr.context-menu-selection td { color: #F8F8F8 !important; } +table.contacts.index tr.context-menu-selection span.tag a { border: 1px solid #EFEFEF; padding: 2px 3px; } +table.contacts.index tr.context-menu-selection span.deal-status { border: 1px solid #EFEFEF; padding: 2px 3px; } + +table.contacts.index tbody tr:hover { background-color:#ffffdd; } + +table.contacts.index td { + vertical-align: top; + color: #666; + font-size: 12px; + padding: 8px 0; + border-bottom: 1px solid #efefef; +} + +table.contacts.index td.checkbox { padding: 12px 0px 0px 5px; } +table.contacts.index td.name { width: 50%; padding-right: 10px;} +table.contacts.index td.info { width: 50%; padding-right: 5px; } +table.contacts.index td.avatar {padding: 10px 10px 10px 5px;} + +table.contacts.index th {padding: 5px 10px 5px 0px; border-bottom: 1px solid #efefef; vertical-align: top;} +table.contacts.index th.title {text-align: right;} +table.contacts.index th.sum {text-align: left;} + +table.contacts.deals.index td.avatar {padding: 10px 10px 10px 10px;} + + +/*table.contacts.index td.avatar {margin-left: 50px;}*/ + +table.contacts.index td.name h1 { font-size: 20px; font-weight: normal; + margin: 0; + padding: 0; +} + +table.contacts.index td.name h1.deal_name { + font-size: 16px; + font-weight: normal; + margin: 0; + padding: 0; +} + +table.contacts.index td.name h1.selected { background-color: #ffb;} +table.contacts.index td.name h2.selected { background-color: #ffb;} + +table.contacts.index td.name h2 { + font-size: 13px; + color: #777; + font-weight: normal; + line-height: 140%; + padding: 0; + margin: 0; + background: none; + border: none; +} + +div.Right td.name div.info { + padding-top: 3px; + margin-left: 42px; +} +/*#edit_tags_form {display: block; margin-top: 10px;}*/ +.notes div.contextual {vertical-align: top;} + +/*Deals*/ + +div#deal-status { + margin-top: 4px; +} + +div.deal-sum { + margin-bottom: 4px; +} + +table.related_deals td.name h4 { letter-spacing: -1px; margin: 0px 0 4px 0; padding: 0; line-height: 1.1em;} + +div#deal-status span.contextual {float: none; padding-left: 0px;} + +span.deal-status { + padding: 3px 4px; + font-size: 10px; + /* display: block;*/ + white-space: nowrap; + margin-right: 4px; +} + +dt.contact { background-image: url(../images/user_suit.png); } + +.icon-money-dollar { background-image: url(../images/money_dollar.png); } +.icon-add-deal { background-image: url(../images/money.png); } +.icon-add-employee { background-image: url(../images/user_suit.png); } +.icon-link-break { background-image: url(../../../images/link_break.png); } +.icon-call { background-image: url(../images/phone.png); } +.icon-meeting { background-image: url(../images/calendar_view_day.png); } +.icon-vcard { background-image: url(../images/vcard.png); } + +/**************************************************************/ +/* THUMBNAILS */ +/**************************************************************/ + +div.wiki img.tumbnail { + border: 1px solid #D7D7D7; + height: 150px; + padding: 4px; + vertical-align: middle; + width: 150px; + background: white; +} + +/**************************************************************/ +/* NOTE_DATA */ +/**************************************************************/ + +table.note_data, table.related_deals { + width: 100%; +} + +table.note_data td.name div.wiki { + margin: 5px 0px 0px 0px; +} + +table.note_data td.avatar { + padding-right: 4px; + padding-left: 0px; +} + +table.note_data td.name { + padding-left: 0px; + vertical-align: top; + width: 100%; +} + +table.note_data a.delete:hover {opacity: 1;} +table.note_data a.delete {opacity: 0.4;} + +table.note_data td.name h4 { + letter-spacing: -1px; + margin: 7px 0 0 0; + padding: 0; + line-height: 1.1em; +} +table.note_data h4 { margin-top: 5px; margin-bottom: 0px;} +table.note_data td.avatar { vertical-align: top; width: 38px; padding-top: 10px;} + +/* +div#notes table.note_data div.contextual {display: none;} +div#notes table.note_data:hover div.contextual {display: inline;} +*/ +table.note_data:hover h4>a.wiki-anchor { display: inline; color: #ddd;} + +.wiki.note a.wiki-anchor {display: none;} + +div.note_data_header table.note_data { margin-bottom: 5px;} + +h2.note_title { margin-bottom: 0px;} + +/* Thumbnails */ + +div.wiki p.thumbnails {margin-top: 12px;} + +img.thumbnail { + border: 1px solid #D7D7D7; + padding: 4px; + margin: 4px; + vertical-align: middle; +} + +/**************************************************************/ +/* CONTACTS_DUBLICATES */ +/**************************************************************/ + +#duplicates ul {margin: 0; padding: 0;} +#duplicates li {list-style-type:none; margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;} +#duplicates img.gravatar {vertical-align: middle; margin: 0 4px 2px 0;} + +#duplicates ul.box {padding: 10px; background-color: #FFEBC1;} + + +/**************************************************************/ +/* CONTACTS_PROJECTS */ +/**************************************************************/ + +#contact_projects ul {margin: 0; padding: 0;} +#contact_projects li {list-style-type:none; margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;} +#contact_projects select {width: 95%; display: block;} +#contact_projects a.delete {opacity: 0.4;} +#contact_projects a.delete:hover {opacity: 1;} +#contact_projects img.gravatar {vertical-align: middle; margin: 0 4px 2px 0;} + + + +/**************************************************************/ +/* CONTACT_DATA */ +/**************************************************************/ +.avatar img.gravatar { + vertical-align: middle; +} + +.tooltip span.tip.contact { + line-height: 11px !important; + font-size: 9px !important; + width: 200px !important; +} + +/**************************************************************/ +/* CONTACT ATTRIBUTES SIDEBAR */ +/**************************************************************/ +table.contact.attributes .gravatar { + vertical-align: middle; + margin: 0 0.5em 0 0; +} \ No newline at end of file diff --git a/assets/stylesheets/contacts_sidebar.css b/assets/stylesheets/contacts_sidebar.css new file mode 100644 index 0000000..f783ef5 --- /dev/null +++ b/assets/stylesheets/contacts_sidebar.css @@ -0,0 +1,10 @@ +#sidebar{ float: right; width: 30%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;} +* html #sidebar{ width: 30%; } +#sidebar h3{ font-size: 14px; margin-top:14px; color: #666; } +#sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; } +* html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; } + +#content { width: 67%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; } +* html #content{ width: 67%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;} +html>body #content { min-height: 600px; overflow: visible; } +* html body #content { height: 600px; } /* IE */ diff --git a/config/locales/cs.yml b/config/locales/cs.yml new file mode 100644 index 0000000..0f58334 --- /dev/null +++ b/config/locales/cs.yml @@ -0,0 +1,150 @@ +cs: + contacts_title: Kontakty + + label_all_people: All people + label_all_companies: All companies + label_all_people_and_companies: All people & companies + label_recently_viewed: Recently viewed + label_gravatar_enabled: Use Gravatar + label_contacts_view_all: Zobraz všechny kontakty + label_contact_files: Soubory + label_task_plural: Úkoly + label_contact_background_info: Poznámky o kontaktu + label_person: Kontakt + label_company: Společnost + label_contact_plural: Kontakty + label_contact_information: Informace o kontaktu + label_contact_edit_information: Úprava kontaktních informací + label_edit_tags: Upravit štítky + label_contact_view: Zobrazit + label_contact_list: Seznam + label_contact_new: Nový + label_at_company: v + label_last_notes: Poslední poznámky + label_tags_plural: Štítky + label_multi_tags_plural: Select multilpe tags + label_single_tag_mode: Single tag + label_multiple_tags_mode: Multiple tags + label_contact_tag: Štítek + label_time_ago: před + label_add_note_plural: Přidat poznámky k + label_note_plural: Poznámky + label_company_employees: Zaměstnanci + label_add_tags_rule: oddělit čárkami + label_search: Vyhledat podle jména + label_note_for: Poznámka pro + label_show_on_map: Zobrazit na mapě + label_add_another_phone: přidat telefon + label_contact_note_plural: Všechny poznámky + label_remove: smazat + label_related_contacts: Související kontakty + label_assigned_to: Odpovědný + label_issue_added: Issue added + label_add_emails_rule: oddělit čárkami + label_add_phones_rule: oddělit čárkami + + label_note_show_extras: Pokročilé (soubory, datum) + label_note_hide_extras: Skrýt pokročilé + label_note_added: Poznámka úspěšně pŕidána + + label_deal_plural: Nabídky + label_contractor_plural: Zprostředkovatelé + label_deal: Nabídka + label_deal_new: Nová nabídka + label_deal_edit_information: Upravit informace o nabídce + label_deal_change_status: Změnit stav + label_statistics: Statistics + + field_note_date: Datum poznámky + + field_deal_name: Jméno + field_deal_background: Poznámky k nabídce + field_deal_contact: Contact + field_deal_price: Sum + field_price: Sum + + field_contact_avatar: Avatar + field_contact_is_company: Kontakt na společnost + field_contact_name: Jméno + field_contact_last_name: Příjmení + field_contact_first_name: Jméno + field_contact_middle_name: Prostřední jméno + field_contact_job_title: Pracovní pozice + field_contact_company: Společnost + field_contact_address: Adresa + field_contact_phone: Telefon + field_contact_email: Email + field_contact_website: Webová stránka + field_contact_skype: Skype + field_contact_status: Status + field_contact_background: Poznámky ke kontaktu + field_contact_tag_names: Štítky + field_first_name: Jméno + field_last_name: Příjmení + field_company: Společnost + field_birthday: Narozeniny + field_contact_department: Oddělení + + field_company_field: Industry + + button_add_note: Přidat poznámku + notice_successful_save: Uloženo + notice_successful_add: Vytvořeno + notice_unsuccessful_save: Chyba při uložení + + project_module_contacts_module: Kontakty + + permission_view_contacts: Zobrazit kontakty + permission_edit_contacts: Upravit kontakty + permission_delete_contacts: Smazat kontakty + permission_view_deals: Zobrazit nabídky + permission_edit_deals: Upravit nabídky + permission_delete_deals: Smazat nabídky + permission_add_notes: Přidat poznámky + permission_delete_notes: Delete notes + permission_delete_own_notes: Delete own notes + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Perion + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts \ No newline at end of file diff --git a/config/locales/de.yml b/config/locales/de.yml new file mode 100644 index 0000000..8c24ce7 --- /dev/null +++ b/config/locales/de.yml @@ -0,0 +1,191 @@ +de: + contacts_title: Kontakte + + label_all_people: Alle Personen + label_all_companies: Alle Unternehmen + label_all_people_and_companies: Alle Personen & Unternehmen + label_recently_viewed: Kürzlich angesehen + label_gravatar_enabled: Gravatar benutzen + label_thumbnails_enabled: Bildvorschau in Anmerkungen anzeigen + label_contacts_view_all: Alle Kontakte anzeigen + label_contact_files: Dateien + label_task_plural: Aufgaben + label_contact_background_info: Hintergrundinformationen + label_person: Person + label_company: Unternehmen + label_contact_plural: Kontaktliste + label_contact_information: Kontaktinformation + label_contact_edit_information: Kontaktinformation bearbeiten + label_edit_tags: Tags bearbeiten + label_contact_view: Anzeigen + label_contact_list: Auflisten + label_contact_new: Neu + label_at_company: bei + label_last_notes: Letzte Anmerkungen + label_tags_plural: Tags + label_contact_tag: Tag + label_time_ago: her + label_add_note_plural: Anmerkung hinzufügen + label_note_plural: Anmerkungen + label_company_employees: Angestellte + label_add_tags_rule: Mit Kommata trennen + label_search: Nach Namen suchen + label_note_for: Anmerkung für + label_show_on_map: Auf Karte anzeigen + label_add_another_phone: Telefon hinzufügen + label_contact_note_plural: Alle Anmerkungen + label_remove: löschen + label_related_contacts: Ähnliche Kontakte + label_assigned_to: Verantwortlich + label_issue_added: Ticket hinzugefügt + label_add_emails_rule: durch Komma separieren + label_add_phones_rule: durch Komma separieren + label_add_employee: Neuer Angestellter + label_merge_dublicate_plural: Zusammenführen + label_dublicate_plural: Mögliche Duplikate + label_dublicate_for_plural: Mögliche Duplikate für + label_add_tag: Neu hinzufügen... + + label_note_show_extras: Erweiterte Optionen (Dateien, Datum) + label_note_hide_extras: Erweiterte Optionen ausblenden + label_note_added: Die Anmerkung wurde hinzugefügt + label_note_read_more: (weiter lesen) + + label_deal_plural: Verkaufschancen + label_contractor_plural: Verkaufskontakte + label_deal: Verkaufschance + label_deal_new: Neue Verkaufschance + label_deal_edit_information: Verkaufschance bearbeiten + label_deal_change_status: Status ändern + label_statistics: Statistiken + + label_deal_status_new: Neu + label_deal_status_first_contact: Erster Kontakt + label_deal_status_negotiations: Verhandlungen + label_deal_status_pending: ausstehend + label_deal_status_won: Gewonnen + label_deal_status_lost: Verloren + + label_created_on: Erstellt + + field_note_date: Datum der Anmerkung + + field_deal_name: Name + field_deal_background: Hintergrundinformation + field_deal_contact: Kontakt + field_deal_price: Preis + field_price: Preis + + field_contact_avatar: Avatar + field_contact_is_company: Unternehmen + field_contact_name: Name + field_contact_last_name: Nachname + field_contact_first_name: Vorname + field_contact_middle_name: Zwischenname + field_contact_job_title: Berufsbezeichnung + field_contact_company: Unternehmen + field_contact_address: Adresse + field_contact_phone: Telefon + field_contact_email: E-Mail + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Status + field_contact_background: Hintergrundinformationen + field_contact_tag_names: Tags + field_first_name: Name + field_last_name: Nachname + field_company: Unternehmen + field_birthday: Geburtstag + field_contact_department: Abteilung + + field_company_field: Industrie + + field_color: Farbe + + button_add_note: Anmerkung hinzufügen + notice_successful_save: Gespeichert + notice_successful_add: Erfolgreich hinzugefügt + notice_unsuccessful_save: Konnte nicht gespeichert werden + notice_successful_merged: Erfolgreich zusammengeführt + + notice_merged_warning: Alle Anmerkungen, Projekte, Tags und Aufgaben die diesem Kontakt zugeordnet sind, werden entsprechend verschoben. Anschliessend wird der Kontakt gelöscht. + + project_module_contacts_module: Kontakte + + permission_view_contacts: Kontakte anzeigen + permission_edit_contacts: Kontakte bearbeiten + permission_delete_contacts: Kontakte löschen + permission_view_deals: Verkaufschance anzeigen + permission_edit_deals: Verkaufschance bearbeiten + permission_delete_deals: Verkaufschance löschen + permission_add_notes: Anmerkungen hinzufügen + permission_delete_notes: Anmerkungen löschen + permission_delete_own_notes: Eigene Anmerkungen löschen + + # 2.0.0 + label_deal_category: Vertriebskategorie + label_deal_category_plural: Vertriebskategorien + label_deal_category_new: Neue Vertriebskategorie + text_deal_category_destroy_assignments: Kategoriezuweisungen entfernen + text_deal_category_destroy_question: "Einige Verkaufschancen (%{count}) sind dieser Kategorie zugeordnet. Was wollen Sie tun?" + text_deal_category_reassign_to: Neuzuweisung der Verkaufschancen zu dieser Kategorie + text_deals_destroy_confirmation: 'Sind Sie sicher, dass Sie die ausgewählten Verkaufschancen löschen wollen?' + label_deal_status_plural: Status + label_deal_status: Status + field_deal_status_is_closed: Geschlossen + label_deal_status_new: Neu + permission_manage_contacts: Aufzählungen + label_sale_funel: Verkaufskanal + label_period: Dauer + label_count: Anzahl + + #2.0.1 + label_user_format: Format Kontaktname + label_my_contact_plural: Mir zugeordnete Kontakte + label_my_deal_plural: Mir zugeordnete offene Verkaufschancen + label_contact_view_all: Alle Kontakte anzeigen + label_deal_view_all: Alle Verkaufschancen anzeigen + + #2.0.2 + label_bulk_edit_selected_contacts: Alle ausgewählten Kontakte bearbeiten + label_bulk_edit_selected_deals: Alle ausgewählten Verkaufschancen bearbeiten + label_bulk_send_mail_selected_contacts: Sende E-Mail zu ausgewählten Kontakten + field_add_tags: Tags hinzufügen + field_delete_tags: Tags löschen + label_send_mail: Sende E-Mail + error_empty_email: E-Mail darf nicht leer sein + permission_send_contacts_mail: Sende E-Mail + field_mail_from: Absender + text_email_macros: Verfügbare Makros %{macro} + field_message: Nachricht + + #2.0.3 + label_add_contact: Neuen Kontakt Projekt hinzufügen + label_contact: Kontakt + label_import: Import + field_age: Alter + label_vcf_import: Import von vCard + label_mail_from: Von + permission_import_contacts: Kontakte importieren + + #2.1.0 + field_company_name: Unternehmen + label_recently_added_contacts: Neuste Kontakte + label_created_by_me: Kontakte hinzugefügt von mir + my_contacts: Meine Kontakte + my_deals: Meine Verkaufschancen + + #2.2.0 + label_note_type_email: E-Mail + label_note_type_call: Anruf + label_note_type_meeting: Besprechung + field_deal_currency: Währung + label_my_contacts_stats: Contacts statistics for this month + label_contacts_created: Hinzugefügte Kontakte + label_deals_created: Hinzugefügte Verkaufschancen + my_contacts_avatars: Meine Kontaktfotos + my_contacts_stats: Statistiken zu Kontakten + label_add_into: Hinzufügen in + label_delete_from: Löschen von + label_show_deaks_tab: Verkaufschancen-Reiter anzeigen + label_show_on_projects_show: Kontakte auf Projekt-Übersichtsseite anzeigen diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..3ca6f72 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,198 @@ +en: + contacts_title: Contacts + + label_all_people: All people + label_all_companies: All companies + label_all_people_and_companies: All people & companies + label_recently_viewed: Recently viewed + label_gravatar_enabled: Use Gravatar + label_thumbnails_enabled: Show image thumbnails in notes + label_max_thumbnail_file_size: Max thumbnailed image size + label_contacts_view_all: View all contacts + label_contact_files: Files + label_task_plural: Tasks + label_contact_background_info: Background info + label_person: Person + label_company: Company + label_contact_plural: Contacts + label_contact_information: Contact Information + label_contact_edit_information: Editing Contact Information + label_edit_tags: Edit tags + label_contact_view: View + label_contact_list: List + label_contact_new: New + label_at_company: at + label_last_notes: Latest notes + label_tags_plural: Tags + label_multi_tags_plural: Select multilpe tags + label_single_tag_mode: Single tag + label_multiple_tags_mode: Multiple tags + label_contact_tag: Tag + label_time_ago: ago + label_add_note_plural: Add note to + label_note_plural: Notes + label_company_employees: Employees + label_add_tags_rule: devide by commas + label_search: Search by name + label_note_for: Note for + label_show_on_map: Show on map + label_add_another_phone: add phone + label_contact_note_plural: All notes + label_remove: delete + label_related_contacts: Related contacts + label_assigned_to: Responsible + label_issue_added: Issue added + label_add_emails_rule: divide by commas + label_add_phones_rule: divide by commas + label_add_employee: New employee + label_merge_dublicate_plural: Merge + label_dublicate_plural: Possible duplicates + label_dublicate_for_plural: Possible duplicates for + label_add_tag: Add new... + + label_note_show_extras: Advanced (type, date, files) + label_note_hide_extras: Hide advanced + label_note_added: The note is successfully added + label_note_read_more: (read more) + + label_deal_plural: Deals + label_contractor_plural: Contactors + label_deal: Deal + label_deal_new: New deal + label_deal_edit_information: Edit deal information + label_deal_change_status: Change status + label_statistics: Statistics + + label_deal_status_new: New + label_deal_status_first_contact: First contact + label_deal_status_negotiations: Negotiations + label_deal_status_pending: Pending + label_deal_status_won: Won + label_deal_status_lost: Lost + + label_created_on: Created on + + field_note_date: Note date + + field_deal_name: Name + field_deal_background: Background + field_deal_contact: Contact + field_deal_price: Sum + field_price: Sum + + field_contact_avatar: Avatar + field_contact_is_company: Company + field_contact_name: Name + field_contact_last_name: Last Name + field_contact_first_name: First Name + field_contact_middle_name: Middle Name + field_contact_job_title: Job title + field_contact_company: Company + field_contact_address: Address + field_contact_phone: Phone + field_contact_email: Email + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Status + field_contact_background: Background + field_contact_tag_names: Tags + field_first_name: Name + field_last_name: Last name + field_company: Company + field_birthday: Birthday + field_contact_department: Department + + + field_company_field: Industry + + field_color: Color + + button_add_note: Add note + notice_successful_save: Saved successfully + notice_successful_add: Successfully created + notice_unsuccessful_save: Save problems + notice_successful_merged: Successfully merged + + notice_merged_warning: All the notes, projects, tags and tasks attached to this person will be moved to choosed below. The contact will then be deleted. + + project_module_contacts_module: Contacts + + permission_view_contacts: View contacts + permission_edit_contacts: Edit contacts + permission_delete_contacts: Delete contacts + permission_view_deals: View deals + permission_edit_deals: Edit deals + permission_delete_deals: Delete deals + permission_add_notes: Add notes + permission_delete_notes: Delete notes + permission_delete_own_notes: Delete own notes + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Period + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts + + #2.1.0 + field_company_name: Company name + label_recently_added_contacts: Recently added contacts + label_created_by_me: Contacts created by me + my_contacts: My contacts + my_deals: My deals + + #2.2.0 + label_note_type_email: Email + label_note_type_call: Call + label_note_type_meeting: Meeting + field_deal_currency: Currency + label_my_contacts_stats: Contacts statistics for this month + label_contacts_created: Contacts created + label_deals_created: Deals created + my_contacts_avatars: My contacts photos + my_contacts_stats: Contacts statistics + label_add_into: Add into + label_delete_from: Delete from + label_show_deaks_tab: Show deals tab + label_show_on_projects_show: Show contacts on projects overview + + #2.2.1 + label_contacts_show_in_list: Show in list \ No newline at end of file diff --git a/config/locales/es.yml b/config/locales/es.yml new file mode 100644 index 0000000..ae795ac --- /dev/null +++ b/config/locales/es.yml @@ -0,0 +1,165 @@ +es: + contacts_title: Contactos + + label_all_people: Toda las personas + label_all_companies: Todas las compañías + label_all_people_and_companies: Todas las personas y compañías + label_recently_viewed: Vistos recientemente + label_gravatar_enabled: Usar Gravatar + label_thumbnails_enabled: Mostrar miniaturas en las notas + label_max_thumbnail_file_size: Maximo tamaño de la miniatura + label_contacts_view_all: Ver todos los contactos + label_contact_files: Ficheros + label_task_plural: Tareas + label_contact_background_info: Transfondo + label_person: Persona + label_company: Compañia + label_contact_plural: Lista de contactos + label_contact_information: Información de contacto + label_contact_edit_information: Editando la información de contacto + label_edit_tags: Cambiar etiquetas + label_contact_view: Ver + label_contact_list: Lista + label_contact_new: Nuevo + label_at_company: en + label_last_notes: Últimas notas + label_tags_plural: Etiquetas + label_multi_tags_plural: Seleccionar varias etiquetas + label_single_tag_mode: Etiqueta + label_multiple_tags_mode: Etiquetas + label_contact_tag: Etiqueta + label_time_ago: antes + label_add_note_plural: Añadir nota a + label_note_plural: Notas + label_company_employees: Empleados + label_add_tags_rule: separadas por comas + label_search: Buscar por nombre + label_note_for: Nota para + label_show_on_map: Mostrar en mapa + label_add_another_phone: añadir teléfono + label_contact_note_plural: Todas las notas + label_remove: borrar + label_related_contacts: Contactos relacionados + label_assigned_to: Responsable + label_issue_added: asunto añadido + label_add_emails_rule: separados por comas + label_add_phones_rule: separados por comas + label_add_employee: Nuevo empleado + label_merge_dublicate_plural: Unir + label_dublicate_plural: Posible duplicado + label_dublicate_for_plural: Posible duplicado para + + label_note_show_extras: Advanzado (ficheros, fechas) + label_note_hide_extras: Esconder avanzado + label_note_added: Se ha añadido la nota correctamente + label_note_read_more: (leer más) + + label_deal_plural: Acuerdos + label_contractor_plural: Contratista + label_deal: Acuerdo + label_deal_new: Nuevo acuerdo + label_deal_edit_information: Cambiar información del acuerdo + label_deal_change_status: Cambiar estado + label_deal_pending: Pendiente + label_deal_won: Ganado + label_deal_lost: Perdido + + + field_note_date: Fecha de la nota + + field_deal_name: Nombre + field_deal_background: Trasfondo + field_deal_contact: Contacto + field_deal_price: Cuenta + field_price: Cuenta + + field_contact_avatar: Avatar + field_contact_is_company: Compañía + field_contact_name: Nombre + field_contact_last_name: Apellido + field_contact_first_name: Nombre + field_contact_middle_name: Segundo nombre + field_contact_job_title: Cargo + field_contact_company: Compañia + field_contact_address: Dirección + field_contact_phone: Teléfono + field_contact_email: Email + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Estado + field_contact_background: Trasfondo + field_contact_tag_names: Etiquetas + field_first_name: Nombre + field_last_name: Apellido + field_company: Compañía + field_birthday: Cumpleaños + field_contact_department: Departmento + + field_company_field: Industria + + field_color: Color + + button_add_note: Añadir nota + notice_successful_save: Guardado correctamente + notice_successful_add: Creado correctamente + notice_unsuccessful_save: Problemas al guardar + notice_successful_merged: Unido correctamente + + notice_merged_warning: Todas las notas, proyectos, etiquetas y tareas asociadas a esta persona se moveran a la persona seleccionada. El contacto será borrado. + + project_module_contacts_module: Contactos + + permission_view_contacts: Ver contactos + permission_edit_contacts: Editar contactos + permission_delete_contacts: Borrar contactos + permission_view_deals: Ver acuerdos + permission_edit_deals: Editar acuerdos + permission_delete_deals: Borrar acuerdos + permission_add_notes: Añadir notas + permission_delete_notes: Borrar notas + permission_delete_own_notes: Borrar notas propias + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Period + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml new file mode 100644 index 0000000..15f459a --- /dev/null +++ b/config/locales/fr.yml @@ -0,0 +1,155 @@ +fr: + contacts_title: Contacts + + label_all_people: All people + label_all_companies: All companies + label_all_people_and_companies: All people & companies + label_recently_viewed: Recently viewed + label_gravatar_enabled: Use Gravatar + label_contacts_view_all: Voir tous les contacts + label_contact_files: Fichiers + label_task_plural: Tâches + label_contact_background_info: Informations / contexte + label_person: Personne + label_company: Compagnie + label_contact_plural: Liste des contacts + label_contact_information: Information sur le contact + label_contact_edit_information: Edition des informations du contact + label_edit_tags: Editer les tags + label_contact_view: Voir + label_contact_list: Lister + label_contact_new: Nouveau + label_at_company: à + label_last_notes: Dernières notes + label_tags_plural: Tags + label_multi_tags_plural: Select multilpe tags + label_single_tag_mode: Single tag + label_multiple_tags_mode: Multiple tags + label_contact_tag: Tag + label_time_ago: il y à + label_add_note_plural: Ajouter une note à + label_note_plural: Notes + label_company_employees: Employés + label_add_tags_rule: séparés par des virgule + label_search: Rechercher par nom + label_note_for: Note pour + label_show_on_map: Afficher sur la carte + label_add_another_phone: ajouter numéro de téléphone + label_contact_note_plural: Toutes les notes + label_remove: supprimer + label_related_contacts: Contacts associés + label_assigned_to: Responsible + label_issue_added: Issue added + label_add_emails_rule: devide by commas + label_add_phones_rule: devide by commas + + label_note_show_extras: Avancés (fichiers, date) + label_note_hide_extras: Masquer avancés + label_note_added: La note a été ajouté avec succès + + label_deal_plural: Affaires + label_contractor_plural: Contacteurs + label_deal: Affaire + label_deal_new: Nouvelle affaire + label_deal_edit_information: Editer affaire + label_deal_change_status: Change status + label_statistics: Statistics + + field_note_date: Date de la note + + field_deal_name: Nom + field_deal_background: Contexte + field_deal_contact: Contact + field_deal_price: Montant + field_price: Montant + + field_contact_avatar: Avatar + field_contact_is_company: Compagnie + field_contact_name: Nom + field_contact_last_name: Nom + field_contact_first_name: Prénom + field_contact_middle_name: Particule (exemple le R. de "James R. Smith") + field_contact_job_title: Métier + field_contact_company: Compagnie + field_contact_address: Adresse + field_contact_phone: Téléphone + field_contact_email: Email + field_contact_website: Site Web + field_contact_skype: Skype + field_contact_notes: Notes + field_contact_status: Statut + field_contact_background: Contexte + field_contact_tag_names: Tags + field_first_name: Name + field_last_name: Last name + field_company: Company + field_birthday: Birthday + field_contact_department: Department + + + field_deal_name: Nom + field_deal_decription: Description + + field_company_field: Secteur d'activité + + button_add_note: Ajouter cette note + notice_successful_save: Sauvegardé avec succès + notice_successful_add: Ajouté avec succès + notice_unsuccessful_save: Impossible d'ajouter + + project_module_contacts_module: Contacts + + permission_view_contacts: Voir les contacts + permission_edit_contacts: Editer les contacts + permission_delete_contacts: Supprimer les contacts + permission_view_deals: Voir les affaires + permission_edit_deals: Editer les affaires + permission_delete_deals: Supprimer les affaires + permission_add_notes: Add notes + permission_delete_notes: Delete notes + permission_delete_own_notes: Delete own notes + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Perion + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts \ No newline at end of file diff --git a/config/locales/it.yml b/config/locales/it.yml new file mode 100644 index 0000000..e91e7d6 --- /dev/null +++ b/config/locales/it.yml @@ -0,0 +1,166 @@ +it: + contacts_title: Contatti + + label_all_people: Tutti + label_all_companies: Tutte le aziende + label_all_people_and_companies: Tutti e tutte le aziende + label_recently_viewed: Visualizzati di recente + label_gravatar_enabled: Usa Gravatar + label_thumbnails_enabled: Mostra miniature belle note + label_max_thumbnail_file_size: Dimensione massima delle note + label_contacts_view_all: Vedi tutti i contatti + label_contact_files: Files + label_task_plural: Compito + label_contact_background_info: Storico contatto + label_person: Persona + label_company: Azienda + label_contact_plural: Lista dei contatti + label_contact_information: Info contatti + label_contact_edit_information: Modifica le informazione del contatto + label_edit_tags: Modifica etichette + label_contact_view: Visualizza + label_contact_list: Lista + label_contact_new: Nuovo + label_at_company: at + label_last_notes: Note recenti + label_tags_plural: Etichette + label_multi_tags_plural: Selezione etichette multiple + label_single_tag_mode: Etichetta singola + label_multiple_tags_mode: Etichette multiple + label_contact_tag: Etichetta + label_time_ago: fa' + label_add_note_plural: Aggiungi note a + label_note_plural: Note + label_company_employees: Impiegati + label_add_tags_rule: separarti da virgola + label_search: Cerca per nome + label_note_for: Nota per + label_show_on_map: Mostra nella mappa + label_add_another_phone: aggiungi telefono + label_contact_note_plural: Tutte le note + label_remove: elimina + label_related_contacts: Contatti collegati + label_assigned_to: Responsabile + label_issue_added: Segnalazione aggiunta + label_add_emails_rule: separati da virgola + label_add_phones_rule: separati da virgola + label_add_employee: Nuovo impiegato + label_merge_dublicate_plural: Merge + label_dublicate_plural: Duplicati ammessi + label_dublicate_for_plural: Possible duplicates for + + label_note_show_extras: Dettagli (files, date) + label_note_hide_extras: Nascondi dettagli + label_note_added: La nota e' stata aggiunta + label_note_read_more: (continua) + + label_deal_plural: Contratti + label_contractor_plural: Contraenti + label_deal: Contratto + label_deal_new: Nuovo contratto + label_deal_edit_information: Modifica dettagli contratto + label_deal_change_status: Cambia stato + label_deal_pending: In trattativa + label_deal_won: Attivo + label_deal_lost: Perso + label_deal_expired: Scaduto + + + field_note_date: Data della nota + + field_deal_name: Nome + field_deal_background: Storico + field_deal_contact: Contatti + field_deal_price: Importo + field_price: Importo + + field_contact_avatar: Avatar + field_contact_is_company: Azienda + field_contact_name: Nome + field_contact_last_name: Cognome + field_contact_first_name: Nome + field_contact_middle_name: Secondo nome + field_contact_job_title: Titolo + field_contact_company: Azienda + field_contact_address: Indirizzo + field_contact_phone: Telefono + field_contact_email: Email + field_contact_website: Sito web + field_contact_skype: Skype + field_contact_status: Stato + field_contact_background: Storico + field_contact_tag_names: Etichette + field_first_name: Nome + field_last_name: Cognome + field_company: Azienda + field_birthday: Compleanno + field_contact_department: Reparto + + field_company_field: Industry + + field_color: Colore + + button_add_note: Aggiungi nota + notice_successful_save: Salvato correttamente + notice_successful_add: Creato correttamente + notice_unsuccessful_save: Salvataggio fallito + notice_successful_merged: Uniti correttamente + + notice_merged_warning: Tutte le note, i progetti, le etichette ed i compiti collegati a questo contatto saranno uniti con il contatto scelto in basso. Quindi questo contatto sara' cancellato. + + project_module_contacts_module: Contatti + + permission_view_contacts: Visualizza contatto + permission_edit_contacts: Modifica contatto + permission_delete_contacts: Elimina contatto + permission_view_deals: Visualizza contratto + permission_edit_deals: Modifica contratto + permission_delete_deals: Elimina contratto + permission_add_notes: Aggiungi note + permission_delete_notes: Elimina note + permission_delete_own_notes: Elimina le proprie note + + # 2.0.0 + label_deal_category: categoria affare + label_deal_category_plural: categorie di offerte + label_deal_category_new: Nuova categoria + text_deal_category_destroy_assignments: Rimuovere categoria incarichi + text_deal_category_destroy_question: "Alcune occasioni (% {count}) vengono assegnati a questa categoria Cosa vuoi fare.? " + text_deal_category_reassign_to: Riassegnare offerte per questa categoria + text_deals_destroy_confirmation: "Sei sicuro di voler cancellare l'affare selezionati (s)?" + label_deal_status_plural: stati affare + label_deal_status: affare di stato + field_deal_status_is_closed: chiuso + label_deal_status_new: Nuovo + permission_manage_contacts: enumerazioni + label_sale_funel: imbuto di vendita + label_period: Data dell'arrivo + label_count: Conte + + # 2.0.1 + label_user_format: Nome del contatto formato + label_my_contact_plural: Contatti assegnato a me + label_my_deal_plural: Offerte aperto assegnato a me + label_contact_view_all: Vedi tutti i contatti + label_deal_view_all: Mostra tutte le offerte + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts \ No newline at end of file diff --git a/config/locales/nl.yml b/config/locales/nl.yml new file mode 100644 index 0000000..813703a --- /dev/null +++ b/config/locales/nl.yml @@ -0,0 +1,148 @@ +nl: + contacts_title: Contacten + + label_all_people: Alle personen + label_all_companies: Alle bedrijven + label_all_people_and_companies: Alle personen & bedrijven + label_recently_viewed: Recent bekeken + label_gravatar_enabled: Gebruik Gravatar + label_contacts_view_all: Bekijk alle contacten + label_contact_files: Bestanden + label_task_plural: Taken + label_contact_background_info: Achtergrondinformatie + label_person: Persoon + label_company: Bedrijf + label_contact_plural: Contactenlijst + label_contact_information: Contactinformatie + label_contact_edit_information: Contactinformatie bewerken + label_edit_tags: Tags bewerken + label_contact_view: Bekijken + label_contact_list: Lijst + label_contact_new: Nieuw + label_at_company: van + label_last_notes: Laatste notities + label_tags_plural: Tags + label_multi_tags_plural: Selecteer meerdere tags + label_single_tag_mode: Enkele tag + label_multiple_tags_mode: Meerdere tags + label_contact_tag: Tag + label_time_ago: geleden + label_add_note_plural: Notitie toevoegen aan + label_note_plural: Notities + label_company_employees: Werknemers + label_add_tags_rule: scheiden op comma + label_search: Zoeken op naam + label_note_for: Notitie voor + label_show_on_map: Toon op kaart + label_add_another_phone: telefoon toevoegen + label_contact_note_plural: Alle notities + label_remove: verwijderen + label_related_contacts: Gerelateerde contacten + label_assigned_to: Verantwoordelijk + label_issue_added: Issue toegevoegd + + label_note_show_extras: Geavanceerd (bestanden, datum) + label_note_hide_extras: Geavanceerd verbergen + label_note_added: Notitie succesvol toegevoegd + + label_deal_plural: Deals + label_contractor_plural: Contractors + label_deal: Deal + label_deal_new: Nieuwe deal + label_deal_edit_information: Deal bewerken + label_deal_change_status: Status veranderen + label_statistics: Statistieken + + field_note_date: Notitiedatum + + field_deal_name: Naam + field_deal_background: Achtergrond + field_deal_contact: Contact + field_deal_price: Som + field_price: Som + + field_contact_avatar: Avatar + field_contact_is_company: Bedrijf + field_contact_name: Naam + field_contact_last_name: Achternaam + field_contact_first_name: Voornaam + field_contact_middle_name: Tussenvoegsel + field_contact_job_title: Functie + field_contact_company: Bedrijf + field_contact_address: Adres + field_contact_phone: Telefoon + field_contact_email: E-Mail + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Status + field_contact_background: Achtergrond + field_contact_tag_names: Tags + field_first_name: Name + field_last_name: Achternaam + field_company: Bedrijf + field_birthday: Geboortedatum + field_contact_department: Afdeling + + field_company_field: Branche + + button_add_note: Notitie toevoegen + notice_successful_save: Succesvol opgeslagen + notice_successful_add: Succesvol aangemaakt + notice_unsuccessful_save: Succesvol opgeslagen + + project_module_contacts_module: Contacten + + permission_view_contacts: Contacten bekijken + permission_edit_contacts: Contacten bewerken + permission_delete_contacts: Contacten verwijderen + permission_view_deals: Deals bekijken + permission_edit_deals: Deals bewerken + permission_delete_deals: Deals verwijderen + permission_add_notes: Notities toevoegen + permission_delete_notes: Verwijder notities + permission_delete_own_notes: Verwijder eigen notities + + # 2.0.0 + label_deal_category: Deal categorie + label_deal_category_plural: Deals categories + label_deal_category_new: Nieuwe categorie + text_deal_category_destroy_assignments: Verwijder categorie toewijzingen + text_deal_category_destroy_question: "Sommige deals (%{count}) vallen onder deze categorie. Wat wil je doen?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Weet je zeker dat je de geselecteerde deal(s) wilt verwijderen?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Gesloten + label_deal_status_new: Nieuw + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Periode + label_count: Aantal + + #2.0.1 + label_user_format: Contact naam formaat + label_my_contact_plural: Contacten aan mij toegewezen + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: Bekijk alle contacts + label_deal_view_all: Bekijk alle deals + + #2.0.2 + label_bulk_edit_selected_contacts: Bewerk alle geselecteerde contacten + label_bulk_edit_selected_deals: Bewerk alle geselecteerde deals + label_bulk_send_mail_selected_contacts: Stuur mail naar geselecteerde contacten + field_add_tags: Voeg tags toe + field_delete_tags: Verwijder tags + label_send_mail: Stuur mail + error_empty_email: Email mag niet leeg zijn + permission_send_contacts_mail: Stuur mail + field_mail_from: Adres afzender + text_email_macros: Beschikbare macro's %{macro} + field_message: Bericht + + #2.0.3 + label_add_contact: Voeg nieuw contact toe aan project + label_contact: Contact + field_age: Leeftijd + label_vcf_import: Importeer vCard + label_mail_from: Afzender + permission_import_contacts: Importeer contacten \ No newline at end of file diff --git a/config/locales/no.yml b/config/locales/no.yml new file mode 100644 index 0000000..cd9ac4d --- /dev/null +++ b/config/locales/no.yml @@ -0,0 +1,195 @@ +"no": + contacts_title: Contacts + + label_all_people: All people + label_all_companies: All companies + label_all_people_and_companies: All people & companies + label_recently_viewed: Recently viewed + label_gravatar_enabled: Use Gravatar + label_thumbnails_enabled: Show image thumbnails in notes + label_max_thumbnail_file_size: Max thumbnailed image size + label_contacts_view_all: View all contacts + label_contact_files: Files + label_task_plural: Tasks + label_contact_background_info: Background info + label_person: Person + label_company: Company + label_contact_plural: Contacts + label_contact_information: Contact Information + label_contact_edit_information: Editing Contact Information + label_edit_tags: Edit tags + label_contact_view: View + label_contact_list: List + label_contact_new: New + label_at_company: at + label_last_notes: Latest notes + label_tags_plural: Tags + label_multi_tags_plural: Select multilpe tags + label_single_tag_mode: Single tag + label_multiple_tags_mode: Multiple tags + label_contact_tag: Tag + label_time_ago: ago + label_add_note_plural: Add note to + label_note_plural: Notes + label_company_employees: Employees + label_add_tags_rule: devide by commas + label_search: Search by name + label_note_for: Note for + label_show_on_map: Show on map + label_add_another_phone: add phone + label_contact_note_plural: All notes + label_remove: delete + label_related_contacts: Related contacts + label_assigned_to: Responsible + label_issue_added: Issue added + label_add_emails_rule: divide by commas + label_add_phones_rule: divide by commas + label_add_employee: New employee + label_merge_dublicate_plural: Merge + label_dublicate_plural: Possible duplicates + label_dublicate_for_plural: Possible duplicates for + label_add_tag: Add new... + + label_note_show_extras: Advanced (type, date, files) + label_note_hide_extras: Hide advanced + label_note_added: The note is successfully added + label_note_read_more: (read more) + + label_deal_plural: Deals + label_contractor_plural: Contactors + label_deal: Deal + label_deal_new: New deal + label_deal_edit_information: Edit deal information + label_deal_change_status: Change status + label_statistics: Statistics + + label_deal_status_new: New + label_deal_status_first_contact: First contact + label_deal_status_negotiations: Negotiations + label_deal_status_pending: Pending + label_deal_status_won: Won + label_deal_status_lost: Lost + + label_created_on: Created on + + field_note_date: Note date + + field_deal_name: Name + field_deal_background: Background + field_deal_contact: Contact + field_deal_price: Sum + field_price: Sum + + field_contact_avatar: Avatar + field_contact_is_company: Company + field_contact_name: Name + field_contact_last_name: Last Name + field_contact_first_name: First Name + field_contact_middle_name: Middle Name + field_contact_job_title: Job title + field_contact_company: Company + field_contact_address: Address + field_contact_phone: Phone + field_contact_email: Email + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Status + field_contact_background: Background + field_contact_tag_names: Tags + field_first_name: Name + field_last_name: Last name + field_company: Company + field_birthday: Birthday + field_contact_department: Department + + + field_company_field: Industry + + field_color: Color + + button_add_note: Add note + notice_successful_save: Saved successfully + notice_successful_add: Successfully created + notice_unsuccessful_save: Save problems + notice_successful_merged: Successfully merged + + notice_merged_warning: All the notes, projects, tags and tasks attached to this person will be moved to choosed below. The contact will then be deleted. + + project_module_contacts_module: Contacts + + permission_view_contacts: View contacts + permission_edit_contacts: Edit contacts + permission_delete_contacts: Delete contacts + permission_view_deals: View deals + permission_edit_deals: Edit deals + permission_delete_deals: Delete deals + permission_add_notes: Add notes + permission_delete_notes: Delete notes + permission_delete_own_notes: Delete own notes + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Period + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts + + #2.1.0 + field_company_name: Company name + label_recently_added_contacts: Recently added contacts + label_created_by_me: Contacts created by me + my_contacts: My contacts + my_deals: My deals + + #2.2.0 + label_note_type_email: Email + label_note_type_call: Call + label_note_type_meeting: Meeting + field_deal_currency: Currency + label_my_contacts_stats: Contacts statistics for this month + label_contacts_created: Contacts created + label_deals_created: Deals created + my_contacts_avatars: My contacts photos + my_contacts_stats: Contacts statistics + label_add_into: Add into + label_delete_from: Delete from + label_show_deaks_tab: Show deals tab + label_show_on_projects_show: Show contacts on projects overview \ No newline at end of file diff --git a/config/locales/pt.yml b/config/locales/pt.yml new file mode 100644 index 0000000..f6f8bcf --- /dev/null +++ b/config/locales/pt.yml @@ -0,0 +1,151 @@ +pt: + contacts_title: Contactos + + label_all_people: All people + label_all_companies: All companies + label_all_people_and_companies: All people & companies + label_recently_viewed: Recently viewed + label_gravatar_enabled: Usar Gravatar + label_contacts_view_all: Ver todos os contactos + label_contact_files: Ficheiros + label_task_plural: Tarefas + label_contact_background_info: Informação de background + label_person: Pessoa + label_company: Empresa + label_contact_plural: Lista de Contactos + label_contact_information: Informação do Contacto + label_contact_edit_information: A editar informação do Contacto + label_edit_tags: Editar tags + label_contact_view: Ver + label_contact_list: Lista + label_contact_new: Novo + label_at_company: em + label_last_notes: Últimas notas + label_tags_plural: Tags + label_multi_tags_plural: Seleccionar múltiplas tags + label_single_tag_mode: Tag individual + label_multiple_tags_mode: Tags múltiplas + label_contact_tag: Tag + label_time_ago: atrás + label_add_note_plural: Adicionar nota a + label_note_plural: Notas + label_company_employees: Funcionários + label_add_tags_rule: separar por vírgulas + label_search: Procurar por nome + label_note_for: Nota para + label_show_on_map: Mostrar no mapa + label_add_another_phone: adicionar telefone + label_contact_note_plural: Todas as notas + label_remove: apagar + label_related_contacts: Contactos relacionados + label_assigned_to: Responsável + label_issue_added: Tarefa adicionada + label_add_emails_rule: separar por vírgulas + label_add_phones_rule: separar por vírgulas + label_add_employee: Novo funcionário + + label_note_show_extras: Avançado (ficheiros, data) + label_note_hide_extras: Esconder avançado + label_note_added: A nota foi acrescentada com sucesso + + label_deal_plural: Acordos + label_contractor_plural: Contratores + label_deal: Acordo + label_deal_new: Novo acordo + label_deal_edit_information: Editar informação do acordo + label_deal_change_status: Alterar estado + label_statistics: Statistics + + field_note_date: Data de nota + + field_deal_name: Nome + field_deal_background: Background + field_deal_contact: Contacto + field_deal_price: Soma + field_price: Soma + + field_contact_avatar: Avatar + field_contact_is_company: Empresa + field_contact_name: Nome + field_contact_last_name: Último Nome + field_contact_first_name: Primeiro Nome + field_contact_middle_name: Nome do Meio + field_contact_job_title: Título de emprego + field_contact_company: Empresa + field_contact_address: Morada + field_contact_phone: Telefone + field_contact_email: Email + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Estado + field_contact_background: Background + field_contact_tag_names: Tags + field_first_name: Nome + field_last_name: Último nome + field_company: Empresa + field_birthday: Data de nascimento + field_contact_department: Departamento + + field_company_field: Indüstria + + button_add_note: Adicionar nota + notice_successful_save: Gravado com sucesso + notice_successful_add: Criado com sucesso + notice_unsuccessful_save: Problemas ao gravar + + project_module_contacts_module: Contactos + + permission_view_contacts: Ver contactos + permission_edit_contacts: Editar contactos + permission_delete_contacts: Apagar contactos + permission_view_deals: Ver acordos + permission_edit_deals: Editar acordos + permission_delete_deals: Apagar acordos + permission_add_notes: Adicionar notas + permission_delete_notes: Delete notes + permission_delete_own_notes: Delete own notes + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Perion + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts \ No newline at end of file diff --git a/config/locales/ru.yml b/config/locales/ru.yml new file mode 100644 index 0000000..5038073 --- /dev/null +++ b/config/locales/ru.yml @@ -0,0 +1,195 @@ +ru: + contacts_title: Контакты + + label_all_people: Все люди + label_all_companies: Все компании + label_all_people_and_companies: Все контакты + label_recently_viewed: Последние просмотренные + label_gravatar_enabled: Использовать Gravatar для контактов + label_thumbnails_enabled: Отображать миниатюры изображений в заметках + label_max_thumbnail_file_size: Максимальный размер отображаемой миниатюры + label_contacts_view_all: Все контакты + label_contact_files: Файлы + label_task_plural: Задачи + label_contact_background_info: Дополнительная информация + label_person: Контакт + label_company: Компания + label_contact_plural: Контакты + label_contact_information: Информация по контакту + label_contact_edit_information: Редактировать информацию по контакту + label_edit_tags: Редактировать тэги + label_contact_view: Просмотр + label_contact_list: Список + label_contact_new: Новый контакт + label_at_company: в + label_last_notes: Последние заметки + label_tags_plural: Тэги + label_multi_tags_plural: Выбор нескольких тэгов + label_single_tag_mode: Один тэг + label_multiple_tags_mode: Несколько тэгов + label_contact_tag: Тэг + label_time_ago: назад + label_add_note_plural: Добавить заметку + label_note_plural: Заметки + label_company_employees: Сотрудники + label_add_tags_rule: несколько тэгов через зяпятую + label_search: Поиск по имени + label_note_for: Заметка для + label_show_on_map: Показать на карте + label_add_another_phone: добавить телефон + label_contact_note_plural: Все заметки + label_remove: удалить + label_related_contacts: Связанные контакты + label_assigned_to: Ответственный + label_issue_added: Добавлена задача + label_add_emails_rule: несколько email через зяпятую + label_add_phones_rule: несколько телефонов через зяпятую + label_add_employee: Новый сотрудник + label_merge_dublicate_plural: Объединить + label_dublicate_plural: Вероятные дубликаты + label_dublicate_for_plural: Вероятные дубликаты для + label_add_tag: Добавить... + + label_note_show_extras: Дополнительно (тип, дата, файлы) + label_note_hide_extras: Скрыть параметры + label_note_added: Заметка успешно добавлена + label_note_read_more: (вся заметка) + + label_deal_plural: Сделки + label_contractor_plural: Участники сделки + label_deal: Сделка + label_deal_new: Новая сделка + label_deal_edit_information: Редактировать информацию по сделке + label_deal_change_status: Сменить статус + label_statistics: Cтатистика + + label_deal_status_new: Новая + label_deal_status_first_contact: Первый контакт + label_deal_status_negotiations: Переговоры + label_deal_status_pending: Принятие решения + label_deal_status_won: Выиграна + label_deal_status_lost: Отвергнута + + label_created_on: Дата создания + + field_note_date: Дата заметки + + field_deal_name: Название + field_deal_background: Описание + field_deal_contact: Участник сделки + field_deal_price: Сумма + field_price: Cумма + + field_contact_avatar: Фото + field_contact_is_company: Компания + field_contact_name: Имя + field_contact_last_name: Фамилия + field_contact_first_name: Имя + field_contact_middle_name: Отчество + field_contact_job_title: Должность + field_contact_company: Компания + field_contact_address: Адрес + field_contact_phone: Телефон + field_contact_email: Email + field_contact_website: Website + field_contact_skype: Skype + field_contact_background: Дополнительная информация + field_contact_status: Статус + field_contact_tag_names: Тэги + field_first_name: Имя + field_last_name: Фамилия + field_company: Компания + field_birthday: День рождения + field_contact_department: Отдел + + field_company_field: Род деятельности + + field_color: Цвет + + button_add_note: Добавить заметку + notice_successful_save: Сохранение успешно завершено + notice_successful_add: Создание успешнео завершено + notice_unsuccessful_save: Невозможно сохранить + notice_successful_merged: Объединение успешно завершено + + notice_merged_warning: Все привязанные проекты, заметки, тэги и задачи этого контакта будут перенесены в выбранный в списке внизу, а этот контакт будет удален. + + project_module_contacts_module: Контакты + + permission_view_contacts: Просмотр контактов + permission_edit_contacts: Редактирование контактов + permission_delete_contacts: Удаление контактов + permission_view_deals: Просмотр сделок + permission_edit_deals: Редактирование сделок + permission_delete_deals: Удаление сделок + permission_add_notes: Добавление заметок + permission_delete_notes: Удаление заметок + permission_delete_own_notes: Удаление своих заметок + + # 2.0.0 + label_deal_category: Категория сделки + label_deal_category_plural: Категории сделок + label_deal_category_new: Новая категория + text_deal_category_destroy_assignments: Удалить назначения категории + text_deal_category_destroy_question: "Несколько сделок (%{count}) назначено в данную категорию. Что Вы хотите предпринять?" + text_deal_category_reassign_to: Переназначить сделки для данной категории + text_deals_destroy_confirmation: 'Вы уверены, что хотите удалить выбранные сделки?' + label_deal_status_plural: Статусы сделок + label_deal_status: Статус cделки + field_deal_status_is_closed: Закрытa + label_deal_status_new: Новый + permission_manage_contacts: Настройка справочников + label_sale_funel: Воронка продаж + label_period: Период + label_count: Кол-во + + #2.0.1 + label_user_format: Формат имени + label_my_contact_plural: Контакты назначенные мне + label_my_deal_plural: Открытые сделки назначенные мне + label_contact_view_all: Посмотреть все контакты + label_deal_view_all: Посмотреть все сделки + + #2.0.2 + label_bulk_edit_selected_contacts: Редактировать все выбранные контакты + label_bulk_edit_selected_deals: Редактировать все выбранные сделки + label_bulk_send_mail_selected_contacts: Отправить письмо выбранным контактам + field_add_tags: Добавить тэги + field_delete_tags: Снять тэги + label_send_mail: Отправить письмо + error_empty_email: Email не может быть пустым + permission_send_contacts_mail: Отправлять письма + field_mail_from: Отправитель + text_email_macros: Макросы %{macro} + field_message: Сообщение + + #2.0.3 + label_add_contact: Создать контакт в проекте + label_contact: Контакт + field_age: Возраст + label_vcf_import: Импорт из vCard + label_mail_from: От + permission_import_contacts: Импорт контактов + + #2.1.0 + field_company_name: Наименование компании + label_recently_added_contacts: Недавно созданные контакты + label_created_by_me: Контакты созданные мной + my_contacts: Мои контакты + my_deals: Мои сделки + + #2.2.0 + label_note_type_email: Email + label_note_type_call: Звонок + label_note_type_meeting: Встреча + field_deal_currency: Валюта + label_my_contacts_stats: Статистика по контактам за этот месяц + label_contacts_created: Добавлено контактов + label_deals_created: Добавлено сделок + my_contacts_avatars: Фотографии моих контактов + my_contacts_stats: Статистика по контактам + label_add_into: Добавить в + label_delete_from: Удалить из + label_show_deaks_tab: Позывать закладку Сделки + label_show_on_projects_show: Показывать контакты на обзоре проекта + diff --git a/config/locales/tr.yml b/config/locales/tr.yml new file mode 100644 index 0000000..6eb99f5 --- /dev/null +++ b/config/locales/tr.yml @@ -0,0 +1,166 @@ +tr: + contacts_title: Kişiler + + label_all_people: Tüm kişiler + label_all_companies: Tüm şirketler + label_all_people_and_companies: Tüm kişi ve şirketler + label_recently_viewed: Son görüntülenen + label_gravatar_enabled: Gravatar kullan + label_thumbnails_enabled: Notlarda küçük resimleri göster + label_max_thumbnail_file_size: Maksimum küçük resim boyutu + label_contacts_view_all: Tüm kişileri göster + label_contact_files: Dosyalar + label_task_plural: İşler + label_contact_background_info: Geçmiş bilgisi + label_person: Kişi + label_company: Şirket + label_contact_plural: Kişi Listesi + label_contact_information: Kişi Bilgileri + label_contact_edit_information: Kişi Bilgisi Düzenleme + label_edit_tags: Etiket düzenleme + label_contact_view: Göster + label_contact_list: Liste + label_contact_new: Yeni + label_at_company: @ + label_last_notes: Son notlar + label_tags_plural: Etiketler + label_multi_tags_plural: Çoklu etiket seç + label_single_tag_mode: Tek etiket + label_multiple_tags_mode: Çoklu etiket + label_contact_tag: Etiket + label_time_ago: önce + label_add_note_plural: Not ekle + label_note_plural: Notlar + label_company_employees: Çalışanlar + label_add_tags_rule: virgül ile ayır + label_search: İsim ile ara + label_note_for: Not -> + label_show_on_map: Haritada göster + label_add_another_phone: telefon ekle + label_contact_note_plural: Tüm notlar + label_remove: Sil + label_related_contacts: İlgili kişiler + label_assigned_to: İlgili kişi + label_issue_added: İş eklendi + label_add_emails_rule: virgül ile ayır + label_add_phones_rule: virgül ile ayır + label_add_employee: Yeni çalışan + label_merge_dublicate_plural: Birleştir + label_dublicate_plural: Potansiyel çift kopyalar + label_dublicate_for_plural: Potansiyel çift kopyalar -> + + label_note_show_extras: İleri düzey (dosya(lar), zaman) + label_note_hide_extras: İleri düzeyi sakla + label_note_added: Not başarıyla eklendi + label_note_read_more: (fazlasını göster) + + label_deal_plural: Anlaşmalar + label_contractor_plural: Yükleniciler + label_deal: Anlaşma + label_deal_new: Yeni anlaşma + label_deal_edit_information: Anlaşma bilgisi düzenle + label_deal_change_status: Durum değiştir + label_deal_pending: Beklemede + label_deal_won: Kazanıldı + label_deal_lost: Kaybedildi + + + field_note_date: Not tarihi + + field_deal_name: İsim + field_deal_background: Bilgi + field_deal_contact: Kişi + field_deal_price: Toplam + field_price: Toplam + + field_contact_avatar: Avatar + field_contact_is_company: Şirket + field_contact_name: İsim + field_contact_last_name: Soyisim + field_contact_first_name: İlk isim + field_contact_middle_name: Orta isim + field_contact_job_title: Görev + field_contact_company: Şirket + field_contact_address: Adres + field_contact_phone: Telefon + field_contact_email: Email + field_contact_website: Website + field_contact_skype: Skype + field_contact_status: Durum + field_contact_background: Bilgi + field_contact_tag_names: Etiketler + field_first_name: İsim + field_last_name: Soyisim + field_company: Şirket + field_birthday: Doğumgünü + field_contact_department: Bölüm + + field_company_field: İş alanı + + field_color: Renk + + button_add_note: Not ekle + notice_successful_save: Başarıyla kaydedildi. + notice_successful_add: Başarıyla yaratıldı. + notice_unsuccessful_save: Kayıt edilemedi. + notice_successful_merged: Başarıyla birleştirildi. + + notice_merged_warning: Bu kişi ile ilgili tüm proje, not ve etiketler seçilen kişiye taşınacak ve bu kişi silinecek. + + project_module_contacts_module: Kişiler + + permission_view_contacts: Kişileri göster + permission_edit_contacts: Kişileri düzenle + permission_delete_contacts: Kişileri sil + permission_view_deals: Anlaşmaları göster + permission_edit_deals: Anlaşmaları düzenle + permission_delete_deals: Anlaşmaları sil + permission_add_notes: Not ekle + permission_delete_notes: Not sil + permission_delete_own_notes: Kendi notlarını sil + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Perion + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Seçilen tüm kişileri düzenle + label_bulk_edit_selected_deals: Seçilen tüm fırsatlar Düzenle + label_bulk_send_mail_selected_contacts: Seçili kişileri, e-posta gönder + field_add_tags: Etiket ekleyin + field_delete_tags: Sil etiketleri + label_send_mail: Posta gönder + error_empty_email: Email boş olamaz + permission_send_contacts_mail: Posta gönder + field_mail_from: Kimden adresi + text_email_macros: Makrolar% {macro} + field_message: Mesaj + + #2.0.3 + label_add_contact: Projenin yeni kişi ekleme + label_contact: İletişim + field_age: Yaş + label_vcf_import: vCard alma + label_mail_from: + permission_import_contacts: Kişileri al + \ No newline at end of file diff --git a/config/locales/zh.yml b/config/locales/zh.yml new file mode 100644 index 0000000..5ea696f --- /dev/null +++ b/config/locales/zh.yml @@ -0,0 +1,163 @@ +zh: + contacts_title: 联系人 + + label_all_people: 所有联系人 + label_all_companies: 所有公司 + label_all_people_and_companies: 所有联系人和公司 + label_recently_viewed: 最近浏览 + label_gravatar_enabled: 使用头像 + label_thumbnails_enabled: 在备注显示缩略图 + label_max_thumbnail_file_size: 最大缩略图尺寸 + label_contacts_view_all: 所有联系人 + label_contact_files: 文件 + label_task_plural: 任务 + label_contact_background_info: 背景信息 + label_person: 个人 + label_company: 公司 + label_contact_plural: 联系人列表 + label_contact_information: 联系人信息 + label_contact_edit_information: 编辑联系人信息 + label_edit_tags: 编辑标签 + label_contact_view: 视图 + label_contact_list: 列表 + label_contact_new: 新建 + label_at_company: 在 + label_last_notes: 最新的备注 + label_tags_plural: 标签 + label_multi_tags_plural: 选中多个标签 + label_single_tag_mode: 单标签 + label_multiple_tags_mode: 多标签 + label_contact_tag: 标签 + label_time_ago: 之前 + label_add_note_plural: 增加备注 + label_note_plural: 备注 + label_company_employees: 雇员 + label_add_tags_rule: 用逗号分割 + label_search: 按名字搜索 + label_note_for: 备注 + label_show_on_map: 在地图上显示 + label_add_another_phone: 添加电话号码 + label_contact_note_plural: 全部备注 + label_remove: 删除 + label_related_contacts: 相关联系人 + label_assigned_to: 接口人 + label_issue_added: 创建的问题 + label_add_emails_rule: 用逗号分隔 + label_add_phones_rule: 用逗号分隔 + + label_add_employee: 新建雇员 + label_merge_dublicate_plural: 合并 + label_dublicate_plural: 可能重复的联系人 + label_dublicate_for_plural: 可能与下列联系人重复 + + label_note_show_extras: 高级 (文件, 日期) + label_note_hide_extras: 隐藏高级选项 + label_note_added: 成功添加了备注 + label_note_read_more: (阅读更多) + + label_deal_plural: 合同 + label_contractor_plural: 接触人 + label_deal: 合同 + label_deal_new: 新合同 + label_deal_edit_information: 编辑合同信息 + label_deal_change_status: 更改状态 + label_deal_pending: 等待 + label_deal_won: 赢得 + label_deal_lost: 丢失 + + + field_note_date: 备注日期 + + field_deal_name: 名称 + field_deal_background: 背景 + field_deal_contact: 联系人 + field_deal_price: 合计 + field_price: 合计 + + field_contact_avatar: 头像 + field_contact_is_company: 公司 + field_contact_name: 名字 + field_contact_last_name: 姓 + field_contact_first_name: 名 + field_contact_middle_name: 中间名 + field_contact_job_title: 职位 + field_contact_company: 公司 + field_contact_address: 地址 + field_contact_phone: 电话 + field_contact_email: 电子邮件 + field_contact_website: 网址 + field_contact_skype: Skype + field_contact_status: 状态 + field_contact_background: 背景 + field_contact_tag_names: 标签 + field_first_name: 名字 + field_last_name: 姓 + field_company: 公司 + field_birthday: 生日 + field_contact_department: 部门 + + field_company_field: 行业 + button_add_note: 增加备注 + notice_successful_save: 已保存 + notice_successful_add: 成功创建 + notice_unsuccessful_save: 无法保存 + notice_successful_merged: 已合并 + + project_module_contacts_module: 联系人 + + permission_view_contacts: 查看联系人 + permission_edit_contacts: 编辑联系人 + permission_delete_contacts: 删除联系人 + permission_view_deals: 查看合同 + permission_edit_deals: 编辑合同 + permission_delete_deals: 删除合同 + permission_add_notes: 增加备注 + permission_delete_notes: 删除备注 + permission_delete_own_notes: 删除自己的备注 + + notice_merged_warning: 所有的备注,项目,标签和附加到这个联系人的任务将被迁徙进以下被选择人,然后此联系人将会被删除. + + # 2.0.0 + label_deal_category: Deal category + label_deal_category_plural: Deals categories + label_deal_category_new: New category + text_deal_category_destroy_assignments: Remove category assignments + text_deal_category_destroy_question: "Some deals (%{count}) are assigned to this category. What do you want to do?" + text_deal_category_reassign_to: Reassign deals to this category + text_deals_destroy_confirmation: 'Are you sure you want to delete the selected deal(s)?' + label_deal_status_plural: Deal statuses + label_deal_status: Deal status + field_deal_status_is_closed: Closed + label_deal_status_new: New + permission_manage_contacts: Enumerations + label_sale_funel: Sales funnel + label_period: Perion + label_count: Count + + #2.0.1 + label_user_format: Contact name format + label_my_contact_plural: Contacts assigned to me + label_my_deal_plural: Open deals assigned to me + label_contact_view_all: View all contacts + label_deal_view_all: View all deals + + #2.0.2 + label_bulk_edit_selected_contacts: Edit all selected contacts + label_bulk_edit_selected_deals: Edit all selected deals + label_bulk_send_mail_selected_contacts: Send mail to selected contacts + field_add_tags: Add tags + field_delete_tags: Delete tags + label_send_mail: Send mail + error_empty_email: Email can not be blank + permission_send_contacts_mail: Send mail + field_mail_from: From address + text_email_macros: Avaliable macros %{macro} + field_message: Message + + #2.0.3 + label_add_contact: Add new contact in project + label_contact: Contact + field_age: Age + label_vcf_import: Import from vCard + label_mail_from: From + permission_import_contacts: Import contacts \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..8f2505c --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,66 @@ +#custom routes for this plugin +ActionController::Routing::Routes.draw do |map| + map.with_options :controller => 'contacts' do |contacts_routes| + contacts_routes.connect "contacts", :conditions => { :method => [:get, :post] }, :action => 'index' #post for live search + contacts_routes.connect "contacts.:format", :conditions => { :method => :get }, :action => 'index' + contacts_routes.connect "contacts.:format", :conditions => { :method => :post }, :action => 'create' + contacts_routes.connect "contacts/:id", :conditions => { :method => :get }, :action => 'show', :id => /\d+/ + contacts_routes.connect "contacts/:id.:format", :conditions => { :method => :get }, :action => 'show', :id => /\d+/ + contacts_routes.connect "contacts/:id.:format", :conditions => { :method => :put }, :action => 'update', :id => /\d+/ + contacts_routes.connect "contacts/:id", :conditions => { :method => :put }, :action => 'update', :id => /\d+/ + contacts_routes.connect "contacts/:id/edit", :conditions => { :method => :get }, :action => 'edit', :id => /\d+/ + contacts_routes.connect "contacts/notes", :conditions => { :method => [:get, :post] }, :action => 'contacts_notes' + contacts_routes.connect "projects/:project_id/contacts", :conditions => { :method => [:get, :post] }, :action => 'index' #post for live search + contacts_routes.connect "projects/:project_id/contacts.:format", :conditions => { :method => :get }, :action => 'index' + contacts_routes.connect "projects/:project_id/contacts.:format", :conditions => { :method => :post }, :action => 'create' + contacts_routes.connect "projects/:project_id/contacts/create", :conditions => { :method => :post }, :action => 'create' + contacts_routes.connect "projects/:project_id/contacts/new", :conditions => { :method => :get }, :action => 'new' + contacts_routes.connect "projects/:project_id/contacts/:id", :conditions => { :method => :get }, :action => 'show', :id => /\d+/ + contacts_routes.connect "projects/:project_id/contacts/:id.:format", :conditions => { :method => :get }, :action => 'show', :id => /\d+/ + contacts_routes.connect "projects/:project_id/contacts/:id/update", :conditions => { :method => :put }, :action => 'update', :id => /\d+/ + contacts_routes.connect "projects/:project_id/contacts/:id/edit", :conditions => { :method => :get }, :action => 'edit', :id => /\d+/ + contacts_routes.connect "projects/:project_id/contacts/:id/destroy", :conditions => { :method => :delete }, :action => 'destroy', :id => /\d+/ + contacts_routes.connect "projects/:project_id/contacts/notes", :conditions => { :method => [:get, :post]}, :action => 'contacts_notes' + contacts_routes.connect "projects/:project_id/contacts/:id/edit_tags", :conditions => { :method => :post }, :action => 'edit_tags' + end + + map.with_options :controller => 'contacts_tasks' do |contacts_issues_routes| + contacts_issues_routes.connect "projects/:project_id/contacts/tasks", :action => 'index' + contacts_issues_routes.connect "projects/:project_id/contacts/:contact_id/new_task", :conditions => { :method => :post }, :action => 'new' + contacts_issues_routes.connect "contacts/tasks", :action => 'index' + end + + map.with_options :controller => 'contacts_duplicates' do |contacts_issues_routes| + contacts_issues_routes.connect "contacts/:contact_id/duplicates" + end + + map.with_options :controller => 'deal_categories' do |categories| + categories.connect 'projects/:project_id/deal_categories/new', :action => 'new' + end + + map.with_options :controller => 'sale_funel' do |sale_funel| + sale_funel.connect 'projects/:project_id/sale_funel', :action => 'index' + sale_funel.connect 'sale_funel', :action => 'index' + end + + + map.with_options :controller => 'deals' do |deals_routes| + deals_routes.connect "deals", :conditions => { :method => :get }, :action => 'index' + deals_routes.connect "projects/:project_id/deals", :action => 'index' + deals_routes.connect "projects/:project_id/deals/create", :conditions => { :method => :post }, :action => 'create' + deals_routes.connect "projects/:project_id/deals/new", :conditions => { :method => :get }, :action => 'new' + deals_routes.connect "deals/:id", :conditions => { :method => :get }, :action => 'show', :id => /\d+/ + deals_routes.connect "deals/:id/update", :conditions => { :method => :post }, :action => 'update', :id => /\d+/ + deals_routes.connect "deals/:id/destroy", :conditions => { :method => :post}, :action => 'destroy', :id => /\d+/ + deals_routes.connect "deals/:id/edit", :conditions => { :method => :get }, :action => 'edit', :id => /\d+/ + end + + map.with_options :controller => 'notes' do |notes_routes| + notes_routes.connect "notes/:note_id", :conditions => { :method => :get }, :action => 'show', :note_id => /\d+/ + notes_routes.connect "notes/show/:note_id", :conditions => { :method => :get }, :action => 'show', :note_id => /\d+/ + notes_routes.connect "notes/:note_id/edit", :conditions => { :method => :get }, :action => 'edit', :note_id => /\d+/ + notes_routes.connect "notes/:note_id/update", :conditions => { :method => :post }, :action => 'update', :note_id => /\d+/ + notes_routes.connect "notes/:note_id/destroy_note", :action => 'destroy_note', :note_id => /\d+/ + end + +end \ No newline at end of file diff --git a/db/migrate/016_create_contacts.rb b/db/migrate/016_create_contacts.rb new file mode 100644 index 0000000..5ccfd9f --- /dev/null +++ b/db/migrate/016_create_contacts.rb @@ -0,0 +1,35 @@ +class CreateContacts < ActiveRecord::Migration + def self.up + create_table :contacts do |t| + t.string :first_name + t.string :last_name + t.string :middle_name + t.string :company + t.text :address + t.string :phone + t.string :email + t.string :website + t.string :skype_name + t.date :birthday + t.string :avatar + t.text :background + t.string :job_title + t.boolean :is_company, :default => false + t.integer :author_id, :default => 0, :null => false + t.integer :assigned_to_id + t.datetime :created_on + t.datetime :updated_on + end + + add_index :contacts, :author_id + add_index :contacts, :is_company + add_index :contacts, :company + add_index :contacts, :first_name + add_index :contacts, :assigned_to_id + + end + + def self.down + drop_table :contacts + end +end diff --git a/db/migrate/017_create_contacts_relations.rb b/db/migrate/017_create_contacts_relations.rb new file mode 100644 index 0000000..abcc019 --- /dev/null +++ b/db/migrate/017_create_contacts_relations.rb @@ -0,0 +1,28 @@ +class CreateContactsRelations < ActiveRecord::Migration + def self.up + create_table :contacts_deals, :id => false do |t| + t.integer :deal_id + t.integer :contact_id + end + add_index :contacts_deals, [:deal_id, :contact_id] + + create_table :contacts_issues, :id => false do |t| + t.integer :issue_id, :default => 0, :null => false + t.integer :contact_id, :default => 0, :null => false + end + add_index :contacts_issues, [:issue_id, :contact_id] + + create_table :contacts_projects, :id => false do |t| + t.integer :project_id, :default => 0, :null => false + t.integer :contact_id, :default => 0, :null => false + end + add_index :contacts_projects, [:project_id, :contact_id] + + end + + def self.down + drop_table :contacts_deals + drop_table :contacts_issues + drop_table :contacts_projects + end +end diff --git a/db/migrate/018_create_deals.rb b/db/migrate/018_create_deals.rb new file mode 100644 index 0000000..ade7cc1 --- /dev/null +++ b/db/migrate/018_create_deals.rb @@ -0,0 +1,30 @@ +class CreateDeals < ActiveRecord::Migration + def self.up + create_table :deals do |t| + t.string :name + t.text :background + t.integer :currency + t.integer :duration + t.decimal :price + t.integer :price_type + t.integer :project_id + t.integer :author_id + t.integer :assigned_to_id + t.integer :status_id, :default => 0, :null => false + t.integer :contact_id + t.integer :category_id + t.datetime :created_on + t.datetime :updated_on + end + add_index :deals, :contact_id + add_index :deals, :project_id + add_index :deals, :status_id + add_index :deals, :author_id + add_index :deals, :category_id + + end + + def self.down + drop_table :deals + end +end diff --git a/db/migrate/019_create_deals_relations.rb b/db/migrate/019_create_deals_relations.rb new file mode 100644 index 0000000..8338416 --- /dev/null +++ b/db/migrate/019_create_deals_relations.rb @@ -0,0 +1,46 @@ +class CreateDealsRelations < ActiveRecord::Migration + def self.up + create_table :deal_categories do |t| + t.string :name, :null => false + t.integer :project_id + end + add_index :deal_categories, :project_id + + create_table :deal_processes do |t| + t.integer :deal_id, :null => false + t.integer :author_id, :null => false + t.integer :old_value + t.integer :value, :null => false + t.datetime :created_at + end + add_index :deal_processes, [:author_id] + add_index :deal_processes, [:deal_id] + + create_table :deal_statuses do |t| + t.string :name, :null => false + t.integer :position + t.boolean :is_default, :default => false, :null => false + t.boolean :is_closed, :default => false, :null => false + t.integer :color, :default => 11184810, :null => false + end + add_index :deal_statuses, [:is_closed] + DealStatus.create(:name => "Pending", :is_closed => false, :is_default => true, :color => "AAAAAA".hex) + DealStatus.create(:name => "Won", :is_closed => true, :is_default => false, :color => "008000".hex) + DealStatus.create(:name => "Lost", :is_closed =>true, :is_default => false, :color => "FF0000".hex) + + + create_table :deal_statuses_projects, :id => false do |t| + t.integer :project_id, :default => 0, :null => false + t.integer :deal_status_id, :default => 0, :null => false + end + add_index :deal_statuses_projects, [:project_id, :deal_status_id] + + end + + def self.down + drop_table :deal_categories + drop_table :deal_processes + drop_table :deal_statuses + drop_table :deal_statuses_projects + end +end diff --git a/db/migrate/020_create_notes.rb b/db/migrate/020_create_notes.rb new file mode 100644 index 0000000..b640268 --- /dev/null +++ b/db/migrate/020_create_notes.rb @@ -0,0 +1,20 @@ +class CreateNotes < ActiveRecord::Migration + def self.up + create_table :notes do |t| + t.string :subject + t.text :content + t.integer :source_id + t.string :source_type + t.integer :author_id + t.datetime :created_on + t.datetime :updated_on + end + add_index :notes, [:source_id, :source_type] + add_index :notes, [:author_id] + + end + + def self.down + drop_table :notes + end +end diff --git a/db/migrate/021_create_tags.rb b/db/migrate/021_create_tags.rb new file mode 100644 index 0000000..52d75be --- /dev/null +++ b/db/migrate/021_create_tags.rb @@ -0,0 +1,39 @@ +class CreateTags < ActiveRecord::Migration + def self.up + unless ActsAsTaggableOn::Tag.table_exists? + create_table :tags do |t| + t.column :name, :string + end + add_index :tags, :name + end + add_column :tags, :color, :integer + add_column :tags, :created_at, :datetime + add_column :tags, :updated_at, :datetime + + unless ActsAsTaggableOn::Tagging.table_exists? + create_table :taggings do |t| + t.column :tag_id, :integer + t.column :taggable_id, :integer + t.column :tagger_id, :integer + t.column :tagger_type, :string + + # You should make sure that the column created is + # long enough to store the required class names. + t.column :taggable_type, :string + t.column :context, :string + + t.column :created_at, :datetime + end + add_index :taggings, :tag_id + add_index :taggings, [:taggable_id, :taggable_type, :context] + end + + end + + def self.down + drop_table :taggings + drop_table :tags + end +end + + diff --git a/db/migrate/022_create_recently_vieweds.rb b/db/migrate/022_create_recently_vieweds.rb new file mode 100644 index 0000000..1f01e37 --- /dev/null +++ b/db/migrate/022_create_recently_vieweds.rb @@ -0,0 +1,18 @@ +class CreateRecentlyVieweds < ActiveRecord::Migration + def self.up + create_table :recently_vieweds do |t| + t.references :viewer + t.references :viewed, :polymorphic => true + t.column :views_count, :integer + t.timestamps + end + + add_index :recently_vieweds, [:viewed_id, :viewed_type] + add_index :recently_vieweds, :viewer_id + + end + + def self.down + drop_table :recently_vieweds + end +end diff --git a/db/migrate/023_create_contacts_settings.rb b/db/migrate/023_create_contacts_settings.rb new file mode 100644 index 0000000..30babd3 --- /dev/null +++ b/db/migrate/023_create_contacts_settings.rb @@ -0,0 +1,15 @@ +class CreateContactsSettings < ActiveRecord::Migration + def self.up + create_table :contacts_settings do |t| + t.column :name, :string + t.column :value, :text + t.column :project_id, :integer + t.column :updated_on, :datetime + end + add_index :contacts_settings, :project_id + end + + def self.down + drop_table :contacts_settings + end +end diff --git a/db/migrate/024_add_type_to_notes.rb b/db/migrate/024_add_type_to_notes.rb new file mode 100644 index 0000000..17542be --- /dev/null +++ b/db/migrate/024_add_type_to_notes.rb @@ -0,0 +1,10 @@ +class AddTypeToNotes < ActiveRecord::Migration + def self.up + add_column :notes, :type_id, :integer + add_index :notes, :type_id + end + + def self.down + remove_column :notes, :type_id + end +end \ No newline at end of file diff --git a/db/migrate/025_add_fields_to_deals.rb b/db/migrate/025_add_fields_to_deals.rb new file mode 100644 index 0000000..d27ae40 --- /dev/null +++ b/db/migrate/025_add_fields_to_deals.rb @@ -0,0 +1,13 @@ +class AddFieldsToDeals < ActiveRecord::Migration + def self.up + change_column :deals, :duration, :integer, :default => 1 + add_column :deals, :due_date, :timestamp + add_column :deals, :probability, :integer + + end + + def self.down + remove_column :deals, :due_date + remove_column :deals, :probability + end +end \ No newline at end of file diff --git a/init.rb b/init.rb new file mode 100644 index 0000000..655f8a4 --- /dev/null +++ b/init.rb @@ -0,0 +1,100 @@ +# Redmine contact plugin + +require 'redmine' +require 'redmine_contacts' + +Redmine::Plugin.register :contacts do + name 'CRM plugin' + author 'RedmineCRM' + description 'This is a CRM plugin for Redmine that can be used to track contacts and deals information' + version '2.2.2' + url 'http://wwww.redminecrm.com' + author_url 'mailto:kirbez@redminecrm.com' + + requires_redmine :version_or_higher => '1.2.2' + + settings :default => { + :use_gravatars => false, + :name_format => :lastname_firstname.to_s, + :auto_thumbnails => true, + :max_thumbnail_file_size => 300 + }, :partial => 'settings/contacts' + + + project_module :contacts_module do + permission :view_contacts, { + :contacts => [:show, :index, :live_search, :contacts_notes, :context_menu], + :contacts_tasks => :index, + :notes => [:show] + } + permission :edit_contacts, { + :contacts => [:edit, :update, :new, :create, :edit_tags], + :notes => [:add_note, :destroy, :edit, :update], + :contacts_tasks => [:new, :add, :delete, :close], + :contacts_duplicates => [:index, :merge, :duplicates], + :contacts_projects => [:add, :delete], + :contacts_vcf => [:load] + } + permission :delete_contacts, :contacts => [:destroy, :bulk_destroy] + permission :send_contacts_mail, :contacts => [:edit_mails, :send_mails, :preview_email] + permission :add_notes, :notes => [:add_note] + permission :delete_notes, :notes => [:destroy, :edit, :update] + permission :delete_own_notes, :notes => [:destroy, :edit, :update] + permission :delete_deals, :deals => [:destroy, :bulk_destroy] + permission :view_deals, { + :deals => [:index, :show, :context_menu], + :sale_funel => [:index], :public => true + } + permission :edit_deals, { + :deals => [:new, :create, :edit, :update, :add_attachment, :bulk_update, :bulk_edit], + :deal_contacts => [:add, :delete], + :notes => [:add_note, :destroy_note] + } + permission :manage_contacts, { + :projects => :settings, + :contacts_settings => :save, + :deal_categories => [:new, :edit, :destroy], + :deal_statuses => [:assing_to_project], :require => :member + } + permission :import_contacts, {} + end + + menu :project_menu, :contacts, {:controller => 'contacts', :action => 'index'}, :caption => :contacts_title, :param => :project_id + menu :project_menu, :deals, {:controller => 'deals', :action => 'index' }, + :caption => :label_deal_plural, + :if => Proc.new{|p| ContactsSetting[:contacts_show_deals_tab, p.id].to_i > 0 }, + :param => :project_id + + menu :application_menu, :contacts, + {:controller => 'contacts', :action => 'index'}, + :caption => :label_contact_plural, + :param => :project_id, + :if => Proc.new{User.current.allowed_to?({:controller => 'contacts', :action => 'index'}, + nil, {:global => true})} + menu :application_menu, :deals, + {:controller => 'deals', :action => 'index'}, + :caption => :label_deal_plural, + :param => :project_id, + :if => Proc.new{User.current.allowed_to?({:controller => 'deals', :action => 'index'}, + nil, {:global => true})} + + + menu :top_menu, :contacts, {:controller => 'contacts', :action => 'index'}, :caption => :contacts_title, :if => Proc.new { + User.current.allowed_to?({:controller => 'contacts', :action => 'index'}, nil, {:global => true}) + } + + menu :admin_menu, :contacts, {:controller => 'settings', :action => 'plugin', :id => "contacts"}, :caption => :contacts_title, :param => :project_id + + activity_provider :contacts, :default => false, :class_name => ['DealNote', 'ContactNote'] + # activity_provider :deals, :default => false, :class_name => ['DealNote'] + + Redmine::Search.map do |search| + search.register :contacts + search.register :deals + search.register :contact_notes + search.register :deal_notes + end + + # activity_provider :contacts, :default => false +end + diff --git a/lib/acts_as_viewable/init.rb b/lib/acts_as_viewable/init.rb new file mode 100644 index 0000000..a6d0cc4 --- /dev/null +++ b/lib/acts_as_viewable/init.rb @@ -0,0 +1,11 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) + +require "lib/acts_as_viewable" + +$LOAD_PATH.shift + +Dispatcher.to_prepare do + unless ActiveRecord::Base.included_modules.include?(ActsAsViewable::Viewable) + ActiveRecord::Base.send(:include, ActsAsViewable::Viewable) + end +end diff --git a/lib/acts_as_viewable/lib/acts_as_viewable.rb b/lib/acts_as_viewable/lib/acts_as_viewable.rb new file mode 100644 index 0000000..6c1bba7 --- /dev/null +++ b/lib/acts_as_viewable/lib/acts_as_viewable.rb @@ -0,0 +1,44 @@ +module ActsAsViewable + module Viewable + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def acts_as_viewable(options = {}) + cattr_accessor :viewable_options + self.viewable_options = {} + viewable_options[:info] = options.delete(:info) || "info".to_sym + + has_many :views, :class_name => "RecentlyViewed", :as => :viewed, :dependent => :delete_all, :order => "#{RecentlyViewed.table_name}.updated_at DESC" + + # attr_reader :info + + send :include, ActsAsViewable::Viewable::InstanceMethods + end + end + + module InstanceMethods + def self.included(base) + base.extend ClassMethods + end + + def viewed(user = User.current) + rv = (self.views.find(:first, :conditions => {:viewer_id => User.current.id}) || self.views.new(:viewer => user)) + rv.increment!(:views_count) + rv.save + end + + module ClassMethods + end + end + + end +end + +Dispatcher.to_prepare do + + unless ActiveRecord::Base.included_modules.include?(ActsAsViewable::Viewable) + ActiveRecord::Base.send(:include, ActsAsViewable::Viewable) + end +end diff --git a/lib/redmine_contacts.rb b/lib/redmine_contacts.rb new file mode 100644 index 0000000..e78dc60 --- /dev/null +++ b/lib/redmine_contacts.rb @@ -0,0 +1,52 @@ +require 'dispatcher' + +require_library_or_gem "acts-as-taggable-on" + +begin + require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick) +rescue LoadError + # RMagick is not available +end + +require 'redmine_contacts/patches/compatibility_patch' + +require 'redmine_contacts/patches/issue_patch' +require 'redmine_contacts/patches/project_patch' +require 'redmine_contacts/patches/tag_patch' +require 'redmine_contacts/patches/mailer_patch' +require 'redmine_contacts/patches/attachments_controller_patch' +require 'redmine_contacts/patches/auto_completes_controller_patch' +require 'redmine_contacts/patches/users_controller_patch' +require 'redmine_contacts/patches/my_controller_patch' +require 'redmine_contacts/patches/custom_fields_helper_patch' + +require 'redmine_contacts/wiki_macros/contacts_wiki_macros' + +# Dir['redmine_contacts/hooks/*.rb'].each {|file| require File.basename(file) } + +require 'redmine_contacts/hooks/views_projects_hook' +require 'redmine_contacts/hooks/views_issues_hook' +require 'redmine_contacts/hooks/views_users_hook' +require 'redmine_contacts/hooks/views_layouts_hook' +require 'redmine_contacts/hooks/views_custom_fields_hook' + +require 'acts_as_viewable/init' + +module RedmineContacts + + # class C08de5896ae031369f5a0f9de2d714bb5 + # def self.name() "redmine_contacts" end + # end + # + # if ActiveRecord::Migration.connection.table_exists?(:schema_migrations) && Engines::Plugin::Migrator.current_version(C08de5896ae031369f5a0f9de2d714bb5) < 20 + # raise "===========================================================\n" + + # "YOU SHOULD USE VERSION < 2.1 PRO OR LIGHT FOR UPDATE PLUGIN\n" + + # "===========================================================" + # end + + def self.settings() Setting[:plugin_contacts] end + +end + + + diff --git a/lib/redmine_contacts/hooks/views_custom_fields_hook.rb b/lib/redmine_contacts/hooks/views_custom_fields_hook.rb new file mode 100644 index 0000000..390f84a --- /dev/null +++ b/lib/redmine_contacts/hooks/views_custom_fields_hook.rb @@ -0,0 +1,7 @@ +module RedmineContacts + module Hooks + class ViewsCustomFieldsHook < Redmine::Hook::ViewListener + render_on :view_custom_fields_form_deal_custom_field, :partial => "deals/custom_field_form" + end + end +end diff --git a/lib/redmine_contacts/hooks/views_issues_hook.rb b/lib/redmine_contacts/hooks/views_issues_hook.rb new file mode 100644 index 0000000..2818348 --- /dev/null +++ b/lib/redmine_contacts/hooks/views_issues_hook.rb @@ -0,0 +1,9 @@ +module RedmineContacts + module Hooks + class ViewsIssuesHook < Redmine::Hook::ViewListener + require "contacts_helper" + + render_on :view_issues_sidebar_planning_bottom, :partial => "issues/contacts", :locals => {:contact_issue => @issue} + end + end +end diff --git a/lib/redmine_contacts/hooks/views_layouts_hook.rb b/lib/redmine_contacts/hooks/views_layouts_hook.rb new file mode 100644 index 0000000..28d913f --- /dev/null +++ b/lib/redmine_contacts/hooks/views_layouts_hook.rb @@ -0,0 +1,9 @@ +module RedmineContacts + module Hooks + class ViewsLayoutsHook < Redmine::Hook::ViewListener + def view_layouts_base_html_head(context={}) + return content_tag(:style, "#admin-menu a.contacts { background-image: url('#{image_path('vcard.png', :plugin => 'redmine_contacts')}'); }", :type => 'text/css') + end + end + end +end \ No newline at end of file diff --git a/lib/redmine_contacts/hooks/views_projects_hook.rb b/lib/redmine_contacts/hooks/views_projects_hook.rb new file mode 100644 index 0000000..dd65745 --- /dev/null +++ b/lib/redmine_contacts/hooks/views_projects_hook.rb @@ -0,0 +1,7 @@ +module RedmineContacts + module Hooks + class ViewsUsersHook < Redmine::Hook::ViewListener + render_on :view_projects_show_left, :partial => "projects/contacts" + end + end +end diff --git a/lib/redmine_contacts/hooks/views_users_hook.rb b/lib/redmine_contacts/hooks/views_users_hook.rb new file mode 100644 index 0000000..1a6cf41 --- /dev/null +++ b/lib/redmine_contacts/hooks/views_users_hook.rb @@ -0,0 +1,9 @@ +include ContactsHelper + +module RedmineContacts + module Hooks + class ViewsUsersHook < Redmine::Hook::ViewListener + render_on :view_account_left_bottom, :partial => "users/contact", :locals => {:user => @user} + end + end +end diff --git a/lib/redmine_contacts/patches/attachments_controller_patch.rb b/lib/redmine_contacts/patches/attachments_controller_patch.rb new file mode 100644 index 0000000..a07f319 --- /dev/null +++ b/lib/redmine_contacts/patches/attachments_controller_patch.rb @@ -0,0 +1,103 @@ +require_dependency 'attachments_controller' +require_dependency 'attachment' + +module RedmineContacts + module Patches + + module AttachmentsControllerPatch + + module InstanceMethods + + def thumbnail + # Dir.mkdir(Attachment.thumbnails_path) if !File.exists?(Attachment.thumbnails_path) + + params[:size] ||= 64 + size = params[:size].to_i + + # "#{@attachment.diskfile}-#{@attachment.digest} + .#{size}x#{size}.thumb.png" + thumb_file_name = File.join(Attachment.storage_path, "#{@attachment.digest}.thumb.#{size}x#{size}.png") + + if FileTest.exists?(thumb_file_name) + send_file(thumb_file_name, :disposition => 'inline', :type => 'image/png', :filename => "#{@attachment.filename}.png") + else + + img = Magick::Image.read(@attachment.diskfile).first + rows, cols = img.rows, img.columns + + thumb = (size < rows || size < cols) ? img.resize_to_fill(size, size) : img + + thumb = thumb.sharpen(0.7,6) + # thumb = thumb.background_color = "White" + # thumb.format = 'PNG' + thumb.write(thumb_file_name) + + send_file(thumb_file_name, :disposition => 'inline', :type => 'image/png', :filename => "#{@attachment.filename}.png") + end + end + + + end + + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + end + + end + + module AttachmentPatch + module ClassMethods + def delete_all_thumbnails + Dir.glob(Attachment.storage_path + "/*.thumb.*").map {|f| File.delete(f)} + end + + end + + module InstanceMethods + + def is_thumbnailable? + (self.is_pdf? && self.filesize < 600.kilobytes) || self.image? + end + + def is_pdf? + self.filename =~ /\.(pdf)$/i + end + + def delete_thumbnails + Dir.glob(self.storage_path + "/#{self.digest}.thumb.*").map {|f| File.delete(f)} + end + + end + + def self.included(base) # :nodoc: + + base.extend(ClassMethods) + + # base.send(:include, InstanceMethods) + + # Same as typing in the class + base.send :include, InstanceMethods + + base.class_eval do + unloadable + after_destroy :delete_thumbnails + + end + end + + end + + + end +end + +Dispatcher.to_prepare do + + unless Attachment.included_modules.include?(RedmineContacts::Patches::AttachmentPatch) + Attachment.send(:include, RedmineContacts::Patches::AttachmentPatch) + end + + unless AttachmentsController.included_modules.include?(RedmineContacts::Patches::AttachmentsControllerPatch) + AttachmentsController.send(:include, RedmineContacts::Patches::AttachmentsControllerPatch) + end + +end \ No newline at end of file diff --git a/lib/redmine_contacts/patches/auto_completes_controller_patch.rb b/lib/redmine_contacts/patches/auto_completes_controller_patch.rb new file mode 100644 index 0000000..4b544e1 --- /dev/null +++ b/lib/redmine_contacts/patches/auto_completes_controller_patch.rb @@ -0,0 +1,27 @@ +require_dependency 'auto_completes_controller' + +module RedmineContacts + module Patches + module AutoCompletesControllerPatch + def self.included(base) + base.send(:include, InstanceMethods) + end + + module InstanceMethods + def contact_tags + @name = params[:q].to_s + @tags = Contact.available_tags :name_like => @name, :limit => 10 + render :layout => false, :partial => 'tag_list' + end + end + end + end +end + +Dispatcher.to_prepare do + + unless AutoCompletesController.included_modules.include?(RedmineContacts::Patches::AutoCompletesControllerPatch) + AutoCompletesController.send(:include, RedmineContacts::Patches::AutoCompletesControllerPatch) + end + +end diff --git a/lib/redmine_contacts/patches/compatibility_patch.rb b/lib/redmine_contacts/patches/compatibility_patch.rb new file mode 100644 index 0000000..a600ce5 --- /dev/null +++ b/lib/redmine_contacts/patches/compatibility_patch.rb @@ -0,0 +1,76 @@ +require_dependency 'application_controller' + +if (Redmine::VERSION.to_a.first(3).join('.') < '1.3.0') + def labelled_fields_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => TabularFormBuilder}) + fields_for(*args, &proc) + end + + def labelled_remote_form_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => TabularFormBuilder}) + remote_form_for(*args, &proc) + end + + def labelled_form_for(*args, &proc) + args << {} unless args.last.is_a?(Hash) + options = args.last + options.merge!({:builder => TabularFormBuilder}) + form_for(*args, &proc) + end +end + + +module RedmineContacts + module Patches + module ApplicationControllerCompatibilityPatch + + module InstanceMethods + + # Find project of id params[:id] + def find_project + @project = Project.find(params[:id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + + # Find project of id params[:project_id] + def find_project_by_project_id + @project = Project.find(params[:project_id]) + rescue ActiveRecord::RecordNotFound + render_404 + end + + # Find a project based on params[:project_id] + # TODO: some subclasses override this, see about merging their logic + def find_optional_project + @project = Project.find(params[:project_id]) unless params[:project_id].blank? + allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true) + allowed ? true : deny_access + rescue ActiveRecord::RecordNotFound + render_404 + end + + end + + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + end + + end + + + + end +end + +Dispatcher.to_prepare do + + if !ApplicationController.included_modules.include?(RedmineContacts::Patches::ApplicationControllerCompatibilityPatch) && (Redmine::VERSION.to_a.first(3).join('.') < '1.1.0') + ApplicationController.send(:include, RedmineContacts::Patches::ApplicationControllerCompatibilityPatch) + end + +end \ No newline at end of file diff --git a/lib/redmine_contacts/patches/custom_fields_helper_patch.rb b/lib/redmine_contacts/patches/custom_fields_helper_patch.rb new file mode 100644 index 0000000..b6d48cf --- /dev/null +++ b/lib/redmine_contacts/patches/custom_fields_helper_patch.rb @@ -0,0 +1,36 @@ +require_dependency 'custom_fields_helper' + +module RedmineContacts + module Patches + + module CustomFieldsHelperPatch + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + + base.class_eval do + alias_method_chain :custom_fields_tabs, :test_tab + end + end + + module InstanceMethods + # Adds a rates tab to the user administration page + def custom_fields_tabs_with_test_tab + tabs = custom_fields_tabs_without_test_tab + tabs << {:name => 'ContactCustomField', :partial => 'custom_fields/index', :label => :label_contact_plural} + tabs << {:name => 'DealCustomField', :partial => 'custom_fields/index', :label => :label_deal_plural} + tabs << {:name => 'NoteCustomField', :partial => 'custom_fields/index', :label => :label_note_plural} + return tabs + end + end + + end + + end +end + +Dispatcher.to_prepare do + unless CustomFieldsHelper.included_modules.include?(RedmineContacts::Patches::CustomFieldsHelperPatch) + CustomFieldsHelper.send(:include, RedmineContacts::Patches::CustomFieldsHelperPatch) + end + +end \ No newline at end of file diff --git a/lib/redmine_contacts/patches/issue_patch.rb b/lib/redmine_contacts/patches/issue_patch.rb new file mode 100644 index 0000000..a35c2d5 --- /dev/null +++ b/lib/redmine_contacts/patches/issue_patch.rb @@ -0,0 +1,30 @@ +require_dependency 'issue' + +# Patches Redmine's Issues dynamically. Adds a relationship +# Issue +has_many+ to ArchDecisionIssue +# Copied from dvandersluis' redmine_resources plugin: +# http://github.com/dvandersluis/redmine_resources/blob/master/lib/resources_issue_patch.rb +module RedmineContacts + module Patches + + module IssuePatch + def self.included(base) # :nodoc: + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + has_and_belongs_to_many :contacts, :order => "last_name, first_name", :uniq => true + end + end + end + + + + end +end + +Dispatcher.to_prepare do + + unless Issue.included_modules.include?(RedmineContacts::Patches::IssuePatch) + Issue.send(:include, RedmineContacts::Patches::IssuePatch) + end +end + diff --git a/lib/redmine_contacts/patches/mailer_patch.rb b/lib/redmine_contacts/patches/mailer_patch.rb new file mode 100644 index 0000000..044b78a --- /dev/null +++ b/lib/redmine_contacts/patches/mailer_patch.rb @@ -0,0 +1,67 @@ +require 'dispatcher' + +module RedmineContacts + module Patches + + module MailerPatch + module ClassMethods + end + + module InstanceMethods + def contacts_note_added(note, parent) + redmine_headers 'X-Project' => note.source.project.identifier, + 'X-Notable-Id' => note.source.id, + 'X-Note-Id' => note.id + message_id note + if parent + recipients (note.source.watcher_recipients + parent.watcher_recipients).uniq + else + recipients note.source.watcher_recipients + end + + subject "[#{note.source.project.name}] - #{parent.name + ' - ' if parent}#{l(:label_note_for)} #{note.source.name}" + + body :note => note, + :note_url => url_for(:controller => 'notes', :action => 'show', :note_id => note.id) + render_multipart('note_added', body) + end + + def contacts_issue_connected(issue, contact) + redmine_headers 'X-Project' => contact.project.identifier, + 'X-Issue-Id' => issue.id, + 'X-Contact-Id' => contact.id + message_id contact + recipients contact.watcher_recipients + subject "[#{contact.projects.first.name}] - #{l(:label_issue_for)} #{contact.name}" + + body :contact => contact, + :issue => issue, + :contact_url => url_for(:controller => contact.class.name.pluralize.downcase, :action => 'show', :project_id => contact.project, :id => contact.id), + :issue_url => url_for(:controller => "issues", :action => "show", :id => issue) + render_multipart('issue_connected', body) + end + + end + + def self.included(receiver) + receiver.extend ClassMethods + receiver.send :include, InstanceMethods + receiver.class_eval do + unloadable + self.instance_variable_get("@inheritable_attributes")[:view_paths] << RAILS_ROOT + "/vendor/plugins/redmine_contacts/app/views" + end + end + + end + + end +end + +Dispatcher.to_prepare do + + unless Mailer.included_modules.include?(RedmineContacts::Patches::MailerPatch) + Mailer.send(:include, RedmineContacts::Patches::MailerPatch) + end + +end + diff --git a/lib/redmine_contacts/patches/my_controller_patch.rb b/lib/redmine_contacts/patches/my_controller_patch.rb new file mode 100644 index 0000000..afb0bc6 --- /dev/null +++ b/lib/redmine_contacts/patches/my_controller_patch.rb @@ -0,0 +1,19 @@ +module RedmineContacts + module Patches + + module MyControllerPatch + def self.included(base) # :nodoc: + base.class_eval do + helper :deals + end + end + end + + end +end + +Dispatcher.to_prepare do + unless MyController.included_modules.include?(RedmineContacts::Patches::MyControllerPatch) + MyController.send(:include, RedmineContacts::Patches::MyControllerPatch) + end +end \ No newline at end of file diff --git a/lib/redmine_contacts/patches/project_patch.rb b/lib/redmine_contacts/patches/project_patch.rb new file mode 100644 index 0000000..bf2dcfb --- /dev/null +++ b/lib/redmine_contacts/patches/project_patch.rb @@ -0,0 +1,22 @@ +module RedmineContacts + module Patches + module ProjectPatch + def self.included(base) # :nodoc: + base.class_eval do + unloadable # Send unloadable so it will not be unloaded in development + has_and_belongs_to_many :contacts, :order => "last_name, first_name", :uniq => true + has_many :deals, :dependent => :delete_all + has_many :deal_categories, :dependent => :delete_all, :order => "#{DealCategory.table_name}.name" + has_and_belongs_to_many :deal_statuses, :order => "#{DealStatus.table_name}.position", :uniq => true + end + end + end + end +end + +Dispatcher.to_prepare do + + unless Project.included_modules.include?(RedmineContacts::Patches::ProjectPatch) + Project.send(:include, RedmineContacts::Patches::ProjectPatch) + end +end \ No newline at end of file diff --git a/lib/redmine_contacts/patches/queries_helper_patch.rb b/lib/redmine_contacts/patches/queries_helper_patch.rb new file mode 100644 index 0000000..c5e84e5 --- /dev/null +++ b/lib/redmine_contacts/patches/queries_helper_patch.rb @@ -0,0 +1,88 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# redmine_tags is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with redmine_tags. If not, see . + +require_dependency 'queries_helper' + +module RedmineTags + module Patches + module QueriesHelperPatch + def self.included(base) + base.send(:include, InstanceMethods) + + base.class_eval do + alias_method :column_content_original, :column_content + alias_method :column_content, :column_content_extended + end + end + + + module InstanceMethods + include TagsHelper + + + # Returns link to the page with issues filtered by specified filters + # === Parameters + # * title = Link title text + # * filters = Filters to be applied (see link_to_filter_options for details) + # * options = (optional) Base options of the link + # === Example + # link_to_filter 'foobar', [[ :tags, '~', 'foobar' ]] + # link_to_filter 'foobar', [[ :tags, '~', 'foobar' ]], :project_id => project + def link_to_filter(title, filters, options = {}) + options.merge! link_to_filter_options(filters) + link_to title, options + end + + + # Returns Hash suitable for passing it to the to_link + # === Parameters + # * filters = Array of arrays. Each child array is an array of strings: + # name, operator and value + # === Example + # link_to 'foobar', link_to_filter_options [[ :tags, '~', 'foobar' ]] + # + # filters = [[ :tags, '~', 'bazbaz' ], [:status_id, 'o']] + # link_to 'bazbaz', link_to_filter_options filters + def link_to_filter_options(filters) + options = { + :controller => 'issues', :action => 'index', :set_filter => 1, + :fields => [], :values => {}, :operators => {} + } + + filters.each do |f| + name, operator, value = f + options[:fields].push name + options[:operators][name] = operator + options[:values][name] = [ value ] + end + + options + end + + + def column_content_extended(column, issue) + if column.name.eql? :tags + column.value(issue).collect{ |t| render_tag_link(t) }.join(', ') + else + column_content_original(column, issue) + end + end + end + end + end +end diff --git a/lib/redmine_contacts/patches/query_patch.rb b/lib/redmine_contacts/patches/query_patch.rb new file mode 100644 index 0000000..16a6941 --- /dev/null +++ b/lib/redmine_contacts/patches/query_patch.rb @@ -0,0 +1,74 @@ +# This file is a part of redmine_tags +# redMine plugin, that adds tagging support. +# +# Copyright (c) 2010 Aleksey V Zapparov AKA ixti +# +# redmine_tags is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# redmine_tags is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with redmine_tags. If not, see . + +require_dependency 'query' + +module RedmineTags + module Patches + module QueryPatch + def self.included(base) + base.send(:include, InstanceMethods) + + base.class_eval do + unloadable + + alias_method :statement_original, :statement + alias_method :statement, :statement_extended + + alias_method :available_filters_original, :available_filters + alias_method :available_filters, :available_filters_extended + + base.add_available_column(QueryColumn.new(:tags)) + end + end + + + module InstanceMethods + def statement_extended + filter = filters.delete 'tags' + clauses = statement_original + + if filter + filters.merge!( 'tags' => filter ) + + values = values_for('tags').clone + compare = operator_for('tags').eql?('=') ? 'IN' : 'NOT IN' + ids_list = Issue.tagged_with(values).collect{ |issue| issue.id }.push(0).join(',') + + clauses << " AND ( #{Issue.table_name}.id #{compare} (#{ids_list}) ) " + end + + clauses + end + + + def available_filters_extended + unless @available_filters + available_filters_original.merge!({ 'tags' => { + :type => :list, + :order => 6, + :values => Issue.available_tags(:project => project).collect{ |t| [t.name, t.name] } + }}) + end + @available_filters + end + end + end + end +end + diff --git a/lib/redmine_contacts/patches/tag_patch.rb b/lib/redmine_contacts/patches/tag_patch.rb new file mode 100644 index 0000000..d7946f9 --- /dev/null +++ b/lib/redmine_contacts/patches/tag_patch.rb @@ -0,0 +1,38 @@ +module RedmineContacts + module Patches + + module TagPatch + module InstanceMethods + def color_name + return "#" + "%06x" % self.color unless self.color.nil? + end + + def color_name=(clr) + self.color = clr.from(1).hex + end + + def assign_color + self.color = (rand * 0xffffff) + end + end + + def self.included(base) # :nodoc: + base.send :include, InstanceMethods + + base.class_eval do + before_create :assign_color + end + end + + end + end +end + + +Dispatcher.to_prepare do + + unless ActsAsTaggableOn::Tag.included_modules.include?(RedmineContacts::Patches::TagPatch) + ActsAsTaggableOn::Tag.send(:include, RedmineContacts::Patches::TagPatch) + end +end + diff --git a/lib/redmine_contacts/patches/users_controller_patch.rb b/lib/redmine_contacts/patches/users_controller_patch.rb new file mode 100644 index 0000000..8bed4a9 --- /dev/null +++ b/lib/redmine_contacts/patches/users_controller_patch.rb @@ -0,0 +1,36 @@ +require_dependency 'users_controller' +require_dependency 'user' + +module RedmineContacts + module Patches + module UsersControllerPatch + + def self.included(base) # :nodoc: + base.send(:include, InstanceMethods) + end + + module InstanceMethods + def new_from_contact + contact = Contact.visible.find(params[:contact_id]) + @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option) + @user.firstname = contact.first_name + @user.lastname = contact.last_name + @user.mail = contact.emails.first + @auth_sources = AuthSource.find(:all) + respond_to do |format| + format.html { render :action => 'new' } + end + end + + end + end + end +end + +Dispatcher.to_prepare do + + unless UsersController.included_modules.include?(RedmineContacts::Patches::UsersControllerPatch) + UsersController.send(:include, RedmineContacts::Patches::UsersControllerPatch) + end + +end \ No newline at end of file diff --git a/lib/redmine_contacts/wiki_macros/contacts_wiki_macros.rb b/lib/redmine_contacts/wiki_macros/contacts_wiki_macros.rb new file mode 100644 index 0000000..8323319 --- /dev/null +++ b/lib/redmine_contacts/wiki_macros/contacts_wiki_macros.rb @@ -0,0 +1,75 @@ +include ContactsHelper +include NotesHelper + +module RedmineContacts + module WikiMacros + + + Redmine::WikiFormatting::Macros.register do + desc "Contact Description Macro" + macro :contact_plain do |obj, args| + args, options = extract_macro_options(args, :parent) + raise 'No or bad arguments.' if args.size != 1 + contact = Contact.find(args.first) + link_to_source(contact) + end + + desc "Contact avatar" + macro :contact_avatar do |obj, args| + args, options = extract_macro_options(args, :parent) + raise 'No or bad arguments.' if args.size != 1 + contact = Contact.find(args.first) + link_to avatar_to(contact, :size => "32"), contact_url(contact), :id => "avatar", :title => contact.name + end + + desc "Contact with avatar" + macro :contact do |obj, args| + args, options = extract_macro_options(args, :parent) + raise 'No or bad arguments.' if args.size != 1 + contact = Contact.find_by_id(args.first) + link_to(avatar_to(contact, :size => "16"), contact_url(contact), :id => "avatar") + ' ' + link_to_source(contact) if contact + end + + desc "Deal" + macro :deal do |obj, args| + args, options = extract_macro_options(args, :parent) + raise 'No or bad arguments.' if args.size != 1 + deal = Deal.find(args.first) + s = '' + s << avatar_to(deal, :size => "16") + " " + s << link_to(deal.full_name, polymorphic_url(deal)) + " " + s << content_tag('span', + h(deal.status), + :style => "background-color:#{deal.status.color_name};color:white;padding: 3px 4px;font-size: 10px;white-space: nowrap;margin-right: 4px;", :class => "deal-status") if deal.status + end + + desc "Thumbnails" + macro :thumbnails do |obj, args| + return "" if obj.blank? + args, options = extract_macro_options(args, :parent) + raise 'No or bad arguments.' if args.size > 1 + + size = "" + size = args.first if args.any? + options[:size] = size.blank? ? "100" : size + options[:class] = "thumbnail" + + s = '' + + s << thumbnails(obj, options) + content_tag(:p, s) if !s.blank? + + end + + end + + end +end diff --git a/lib/tasks/contacts_email.rake b/lib/tasks/contacts_email.rake new file mode 100644 index 0000000..233bf59 --- /dev/null +++ b/lib/tasks/contacts_email.rake @@ -0,0 +1,172 @@ +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +namespace :redmine do + namespace :email do + namespace :contacts do + + desc <<-END_DESC +Read an email from standard input. + +General options: + unknown_user=ACTION how to handle emails from an unknown user + ACTION can be one of the following values: + ignore: email is ignored (default) + accept: accept as anonymous user + create: create a user account + no_permission_check=1 disable permission checking when receiving + the email + +Issue attributes control options: + project=PROJECT identifier of the target project + status=STATUS name of the target status + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + rake redmine:email:read RAILS_ENV="production" < raw_email + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + rake redmine:email:contacts:read RAILS_ENV="production" \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority < raw_email +END_DESC + + task :read => :environment do + options = { :contact => {} } + # %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + # options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + # options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] + # options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] + + ContactsMailer.receive(STDIN.read, options) + end + + desc <<-END_DESC +Read emails from an IMAP server. + +General options: + unknown_user=ACTION how to handle emails from an unknown user + ACTION can be one of the following values: + ignore: email is ignored (default) + accept: accept as anonymous user + create: create a user account + no_permission_check=1 disable permission checking when receiving + the email + +Available IMAP options: + host=HOST IMAP server host (default: 127.0.0.1) + port=PORT IMAP server port (default: 143) + ssl=SSL Use SSL? (default: false) + username=USERNAME IMAP account + password=PASSWORD IMAP password + folder=FOLDER IMAP folder to read (default: INBOX) + +Issue attributes control options: + project=PROJECT identifier of the target project + status=STATUS name of the target status + tracker=TRACKER name of the target tracker + category=CATEGORY name of the target category + priority=PRIORITY name of the target priority + allow_override=ATTRS allow email content to override attributes + specified by previous options + ATTRS is a comma separated list of attributes + +Processed emails control options: + move_on_success=MAILBOX move emails that were successfully received + to MAILBOX instead of deleting them + move_on_failure=MAILBOX move emails that were ignored to MAILBOX + +Examples: + # No project specified. Emails MUST contain the 'Project' keyword: + + rake redmine:email:contacts:receive_imap RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx + + + # Fixed project and default tracker specified, but emails can override + # both tracker and priority attributes: + + rake redmine:email:receive_imap RAILS_ENV="production" \\ + host=imap.foo.bar username=redmine@example.net password=xxx ssl=1 \\ + project=foo \\ + tracker=bug \\ + allow_override=tracker,priority +END_DESC + + task :receive_imap => :environment do + imap_options = {:host => ENV['host'], + :port => ENV['port'], + :ssl => ENV['ssl'], + :username => ENV['username'], + :password => ENV['password'], + :folder => ENV['folder'], + :move_on_success => ENV['move_on_success'], + :move_on_failure => ENV['move_on_failure']} + + options = { :issue => {} } + %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] + options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] + + ContactsMailer.check_imap(imap_options, options) + end + + desc <<-END_DESC +Read emails from an POP3 server. + +Available POP3 options: + host=HOST POP3 server host (default: 127.0.0.1) + port=PORT POP3 server port (default: 110) + username=USERNAME POP3 account + password=PASSWORD POP3 password + apop=1 use APOP authentication (default: false) + delete_unprocessed=1 delete messages that could not be processed + successfully from the server (default + behaviour is to leave them on the server) + +See redmine:email:contacts:receive_imap for more options and examples. +END_DESC + + task :receive_pop3 => :environment do + pop_options = {:host => ENV['host'], + :port => ENV['port'], + :apop => ENV['apop'], + :username => ENV['username'], + :password => ENV['password'], + :delete_unprocessed => ENV['delete_unprocessed']} + + options = { :issue => {} } + %w(project status tracker category priority).each { |a| options[:issue][a.to_sym] = ENV[a] if ENV[a] } + options[:allow_override] = ENV['allow_override'] if ENV['allow_override'] + options[:unknown_user] = ENV['unknown_user'] if ENV['unknown_user'] + options[:no_permission_check] = ENV['no_permission_check'] if ENV['no_permission_check'] + + ContactsMailer.check_pop3(pop_options, options) + end + + end + end +end diff --git a/test/fixtures/contacts.yml b/test/fixtures/contacts.yml new file mode 100644 index 0000000..59649ef --- /dev/null +++ b/test/fixtures/contacts.yml @@ -0,0 +1,30 @@ +contact_one: + id: 1 + first_name: Ivan + last_name: Ivanov + middle_name: Ivanovich + company: Domoway + email: ivan@mail.com + website: http://ivanov.com + +contact_two: + id: 2 + first_name: Marat + last_name: Aminov + middle_name: Ivanovich + job_title: CEO + company: Domoway + email: marat@mail.ru, marat@mail.com + skype_name: marat_aminov + background: Marat is a famous writer an reader + +contact_three: + id: 3 + first_name: Domoway + job_title: web project + is_company: true + email: "domoway@mail.ru, marat@mail.ru" + background: Realy cool project + website: domoway.ru + + diff --git a/test/fixtures/contacts_issues.yml b/test/fixtures/contacts_issues.yml new file mode 100644 index 0000000..8973e74 --- /dev/null +++ b/test/fixtures/contacts_issues.yml @@ -0,0 +1,10 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + issue_id: 1 + contact_id: 1 +two: + issue_id: 2 + contact_id: 2 +three: + issue_id: 1 + contact_id: 2 \ No newline at end of file diff --git a/test/fixtures/contacts_mailer/fwd_new_note_html.eml b/test/fixtures/contacts_mailer/fwd_new_note_html.eml new file mode 100644 index 0000000..39e17ef --- /dev/null +++ b/test/fixtures/contacts_mailer/fwd_new_note_html.eml @@ -0,0 +1,128 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "redMine Admin" +To: +Subject: New note from forwarded html email +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="Apple-Mail=_A369DB54-0A71-4F82-915A-340994835550" +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + + +--Apple-Mail=_A369DB54-0A71-4F82-915A-340994835550 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; + charset=utf-8 + + + +Begin forwarded message: + +> From: Marat Aminov +> Subject: Lorem ipsum dolor sit amet, consectetuer +> Date: Wed, 5 Oct 2011 00:16:18 +> To: admin@somenet.foo +>=20 +> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas = +imperdiet=20 +> turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus=20= + +> blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent = +taciti=20 +> sociosqu ad litora torquent per conubia nostra, per inceptos = +himenaeos. In=20 +> in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in = +dolor. Cras=20 +> sagittis odio eu lacus.=20 +>=20 +> Aliquam sem tortor, consequat sit amet, vestibulum id, iaculis at, = +lectus.=20 +> Fusce tortor libero, congue ut, euismod nec, luctus=20 +>=20 +> eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, = +tristique=20 +> sed, mauris. Pellentesque habitant morbi tristique senectus et netus = +et=20 +> malesuada fames ac turpis egestas. Quisque sit amet libero. In hac = +habitasse=20 +> platea dictumst. +>=20 +> Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque=20= + +> sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed = +lorem.=20 +> Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum = +et,=20 +> dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat = +sed,=20 +> massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo=20= + +> pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. + + +--Apple-Mail=_A369DB54-0A71-4F82-915A-340994835550 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/html; + charset=utf-8 + +

Begin forwarded message:

From: Marat Aminov <marat@mail.com>
Subject: = +Lorem ipsum dolor sit amet, = +consectetuer
Date: Wed, 5 Oct 2011 00:16:18
Lorem ipsum dolor = +sit amet, consectetuer adipiscing elit. Maecenas = +imperdiet 
turpis et odio. Integer eget pede vel dolor = +euismod varius. Phasellus 
blandit eleifend augue. = +Nulla facilisi. Duis id diam. Class aptent = +taciti 
sociosqu ad litora torquent per conubia nostra, = +per inceptos himenaeos. In 
in urna sed tellus aliquet = +lobortis. Morbi scelerisque tortor in dolor. = +Cras 
sagittis odio eu = +lacus. 

  • Aliquam sem tortor, consequat sit amet, = +vestibulum id, iaculis at, lectus. 
  • Fusce tortor = +libero, congue ut, euismod nec, = +luctus 

eget, eros. Pellentesque = +tortor enim, feugiat in, dignissim eget, tristique 
sed, = +mauris. Pellentesque habitant morbi tristique senectus et netus = +et 
malesuada fames ac turpis egestas. Quisque sit amet = +libero. In hac habitasse 
platea = +dictumst.

Nulla et nunc. Duis pede. Donec et = +ipsum. Nam ut dui tincidunt neque 
sollicitudin iaculis. = +Duis vitae dolor. Vestibulum eget massa. Sed = +lorem. 
Nullam volutpat cursus erat. Cras felis dolor, = +lacinia quis, rutrum et, 
dictum et, ligula. Sed erat = +nibh, gravida in, accumsan non, placerat sed, 
massa. Sed = +sodales, ante fermentum ultricies sollicitudin, massa = +leo 
pulvinar dui, a gravida orci mi eget odio. Nunc a = +lacus.

= + +--Apple-Mail=_A369DB54-0A71-4F82-915A-340994835550-- diff --git a/test/fixtures/contacts_mailer/fwd_new_note_plain.eml b/test/fixtures/contacts_mailer/fwd_new_note_plain.eml new file mode 100644 index 0000000..3230a5a --- /dev/null +++ b/test/fixtures/contacts_mailer/fwd_new_note_plain.eml @@ -0,0 +1,51 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "redMine Admin" +To: +Subject: New note from forwarded email +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + + +---- Original message ---- +> From: "Marat Aminov" marat@mail.ru +> To: "Ivan Ivanov" contacts@somenet.foo +> Date: Sun, 26 Jun 2011 12:28:07 +0200 +> Subject: Test subject + +> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet +> turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus +> blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti +> sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In +> in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras +> sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum +> id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus +> eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique +> sed, mauris. Pellentesque habitant morbi tristique senectus et netus et +> malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse +> platea dictumst. +> +> Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque +> sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. +> Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, +> dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, +> massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo +> pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. +> +> Project: onlinestore +> Tracker: Feature Request +> category: stock management +> priority: URGENT +> \ No newline at end of file diff --git a/test/fixtures/contacts_mailer/new_deal_note_by_id.eml b/test/fixtures/contacts_mailer/new_deal_note_by_id.eml new file mode 100644 index 0000000..099849d --- /dev/null +++ b/test/fixtures/contacts_mailer/new_deal_note_by_id.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "redMine Admin" +To: +Bcc: +Subject: New note from email +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet +turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus +blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti +sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In +in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras +sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum +id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus +eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique +sed, mauris. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse +platea dictumst. + +Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque +sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. +Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, +dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, +massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo +pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. + diff --git a/test/fixtures/contacts_mailer/new_deny_note.eml b/test/fixtures/contacts_mailer/new_deny_note.eml new file mode 100644 index 0000000..6992cbb --- /dev/null +++ b/test/fixtures/contacts_mailer/new_deny_note.eml @@ -0,0 +1,44 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "redMine Admin" +To: +Bcc: +Subject: New note from email +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet +turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus +blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti +sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In +in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras +sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum +id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus +eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique +sed, mauris. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse +platea dictumst. + +Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque +sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. +Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, +dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, +massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo +pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. + +Project: onlinestore +Tracker: Feature Request +category: stock management +priority: URGENT diff --git a/test/fixtures/contacts_mailer/new_note.eml b/test/fixtures/contacts_mailer/new_note.eml new file mode 100644 index 0000000..ebb35f8 --- /dev/null +++ b/test/fixtures/contacts_mailer/new_note.eml @@ -0,0 +1,44 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "redMine Admin" +To: +Bcc: +Subject: New note from email +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet +turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus +blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti +sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In +in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras +sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum +id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus +eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique +sed, mauris. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse +platea dictumst. + +Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque +sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. +Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, +dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, +massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo +pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. + +Project: onlinestore +Tracker: Feature Request +category: stock management +priority: URGENT diff --git a/test/fixtures/contacts_mailer/new_note_by_id.eml b/test/fixtures/contacts_mailer/new_note_by_id.eml new file mode 100644 index 0000000..fade935 --- /dev/null +++ b/test/fixtures/contacts_mailer/new_note_by_id.eml @@ -0,0 +1,43 @@ +Return-Path: +Received: from osiris ([127.0.0.1]) + by OSIRIS + with hMailServer ; Sun, 22 Jun 2008 12:28:07 +0200 +Message-ID: <000501c8d452$a95cd7e0$0a00a8c0@osiris> +From: "redMine Admin" +To: +Subject: New note from email +Date: Sun, 22 Jun 2008 12:28:07 +0200 +MIME-Version: 1.0 +Content-Type: text/plain; + format=flowed; + charset="iso-8859-1"; + reply-type=original +Content-Transfer-Encoding: 7bit +X-Priority: 3 +X-MSMail-Priority: Normal +X-Mailer: Microsoft Outlook Express 6.00.2900.2869 +X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.2869 + +Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas imperdiet +turpis et odio. Integer eget pede vel dolor euismod varius. Phasellus +blandit eleifend augue. Nulla facilisi. Duis id diam. Class aptent taciti +sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In +in urna sed tellus aliquet lobortis. Morbi scelerisque tortor in dolor. Cras +sagittis odio eu lacus. Aliquam sem tortor, consequat sit amet, vestibulum +id, iaculis at, lectus. Fusce tortor libero, congue ut, euismod nec, luctus +eget, eros. Pellentesque tortor enim, feugiat in, dignissim eget, tristique +sed, mauris. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Quisque sit amet libero. In hac habitasse +platea dictumst. + +Nulla et nunc. Duis pede. Donec et ipsum. Nam ut dui tincidunt neque +sollicitudin iaculis. Duis vitae dolor. Vestibulum eget massa. Sed lorem. +Nullam volutpat cursus erat. Cras felis dolor, lacinia quis, rutrum et, +dictum et, ligula. Sed erat nibh, gravida in, accumsan non, placerat sed, +massa. Sed sodales, ante fermentum ultricies sollicitudin, massa leo +pulvinar dui, a gravida orci mi eget odio. Nunc a lacus. + +Project: onlinestore +Tracker: Feature Request +category: stock management +priority: URGENT diff --git a/test/fixtures/contacts_projects.yml b/test/fixtures/contacts_projects.yml new file mode 100644 index 0000000..0f4112e --- /dev/null +++ b/test/fixtures/contacts_projects.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +contact_project_one: + project_id: 1 + contact_id: 1 +contact_project_two: + project_id: 1 + contact_id: 2 +contact_project_three: + project_id: 1 + contact_id: 3 +contact_project_0004: + project_id: 2 + contact_id: 1 \ No newline at end of file diff --git a/test/fixtures/contacts_settings.yml b/test/fixtures/contacts_settings.yml new file mode 100644 index 0000000..efbcdeb --- /dev/null +++ b/test/fixtures/contacts_settings.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 + name: MyString + value: MyText + project_id: 1 + updated_on: 2011-10-30 01:48:30 +two: + id: 2 + name: MyString + value: MyText + project_id: 1 + updated_on: 2011-10-30 01:48:30 diff --git a/test/fixtures/deal_categories.yml b/test/fixtures/deal_categories.yml new file mode 100644 index 0000000..be9d87b --- /dev/null +++ b/test/fixtures/deal_categories.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 + name: Design + project_id: 1 +two: + id: 2 + name: Developing + project_id: 1 diff --git a/test/fixtures/deal_processes.yml b/test/fixtures/deal_processes.yml new file mode 100644 index 0000000..e69de29 diff --git a/test/fixtures/deal_statuses.yml b/test/fixtures/deal_statuses.yml new file mode 100644 index 0000000..df08c37 --- /dev/null +++ b/test/fixtures/deal_statuses.yml @@ -0,0 +1,20 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +new: + id: 1 + name: Pending + color: 11184810 + is_default: true + is_closed: false +won: + id: 2 + name: Won + color: 32768 + is_default: false + is_closed: true +lost: + id: 3 + name: Lost + color: 16711680 + is_default: false + is_closed: true + diff --git a/test/fixtures/deals.yml b/test/fixtures/deals.yml new file mode 100644 index 0000000..36116b2 --- /dev/null +++ b/test/fixtures/deals.yml @@ -0,0 +1,51 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +deal_001: + id: 1 + name: First deal with contacts + contact_id: 1 + project_id: 1 + background: Test deal + author_id: 1 + status_id: 1 + assigned_to_id: 1 + price: 3000 + +deal_002: + id: 2 + name: Second deal with contacts + contact_id: 3 + project_id: 2 + background: Test deal two + author_id: 2 + status_id: 3 + price: 10000 + +deal_003: + id: 3 + name: Delevelop redmine plugin + contact_id: 3 + project_id: 2 + background: Cross project deal + author_id: 2 + status_id: 2 + price: 5000 + +deal_004: + id: 4 + name: Deal without contact + project_id: 2 + background: Deal without contact + author_id: 2 + status_id: 1 + price: 25500 + +deal_005: + id: 5 + name: Closed deal + contact_id: 3 + project_id: 2 + background: Closed deal + author_id: 2 + status_id: 2 + price: 25500 + \ No newline at end of file diff --git a/test/fixtures/files/image.jpg b/test/fixtures/files/image.jpg new file mode 100644 index 0000000..b2444a9 Binary files /dev/null and b/test/fixtures/files/image.jpg differ diff --git a/test/fixtures/notes.yml b/test/fixtures/notes.yml new file mode 100644 index 0000000..c144109 --- /dev/null +++ b/test/fixtures/notes.yml @@ -0,0 +1,29 @@ +# Read about fixtures at http:__ar.rubyonrails.org_classes_Fixtures.html +note_001: + id: 1 + subject: Note 1 subject + content: Note 1 _content_ with wiki *syntax* + source_id: 1 + source_type: Contact + author_id: 1 +note_002: + id: 2 + subject: Note 2 subject + content: Note 2 _content_ with wiki *syntax* + source_id: 1 + source_type: Contact + author_id: 2 +note_003: + id: 3 + subject: Note 3 subject + content: Note 3 _content_ with wiki *syntax* + source_id: 2 + source_type: Contact + author_id: 1 +note_004: + id: 4 + subject: Note 4 subject + content: Note 4 _content_ with wiki *syntax* + source_id: 3 + source_type: Contact + author_id: 2 diff --git a/test/fixtures/recently_vieweds.yml b/test/fixtures/recently_vieweds.yml new file mode 100644 index 0000000..06cd0c5 --- /dev/null +++ b/test/fixtures/recently_vieweds.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 + viewed_id: + viewed_type: + viewer_id: + created_at: 2011-04-08 15:29:20 +two: + id: 2 + viewed_id: + viewed_type: + viewer_id: + created_at: 2011-04-08 15:29:20 diff --git a/test/fixtures/taggings.yml b/test/fixtures/taggings.yml new file mode 100644 index 0000000..e59e4c4 --- /dev/null +++ b/test/fixtures/taggings.yml @@ -0,0 +1,25 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +tagging_001: + tag_id: 1 + taggable_id: 1 + taggable_type: Contact + context: tags + +tagging_002: + tag_id: 2 + taggable_id: 2 + taggable_type: Contact + context: tags + +tagging_003: + tag_id: 1 + taggable_id: 3 + taggable_type: Contact + context: tags + +tagging_004: + tag_id: 2 + taggable_id: 3 + taggable_type: Contact + context: tags + \ No newline at end of file diff --git a/test/fixtures/tags.yml b/test/fixtures/tags.yml new file mode 100644 index 0000000..dd717ad --- /dev/null +++ b/test/fixtures/tags.yml @@ -0,0 +1,9 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +tag_one: + id: 1 + name: main + color: <%= '3333ff'.hex %> +tag_two: + id: 2 + name: test + color: <%= 'ff3333'.hex %> diff --git a/test/functional/contacts_controller_test.rb b/test/functional/contacts_controller_test.rb new file mode 100644 index 0000000..41ec02a --- /dev/null +++ b/test/functional/contacts_controller_test.rb @@ -0,0 +1,408 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'contacts_controller' + +class ContactsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + # TODO: Test for delete tags in update action + + def setup + RedmineContacts::TestCase.prepare + + @controller = ContactsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + end + + test "should get index" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + Setting.default_language = 'en' + + get :index + assert_response :success + assert_template :index + assert_not_nil assigns(:contacts) + assert_not_nil assigns(:tags) + assert_nil assigns(:project) + assert_tag :tag => 'a', :content => /Domoway/ + assert_tag :tag => 'a', :content => /Marat/ + assert_tag :tag => 'h3', :content => /Tags/ + assert_tag :tag => 'h3', :content => /Recently viewed/ + + assert_select 'div#tags span#single_tags span.tag a', "main(2)" + assert_select 'div#tags span#single_tags span.tag a', "test(2)" + + # private projects hidden + # assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ + # assert_no_tag :tag => 'a', :content => /Issue on project 2/ + # project column + # assert_tag :tag => 'th', :content => /Project/ + end + + test "should get index in project" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + Setting.default_language = 'en' + + get :index, :project_id => 1 + assert_response :success + assert_template :index + assert_not_nil assigns(:contacts) + assert_not_nil assigns(:project) + assert_tag :tag => 'a', :content => /Domoway/ + assert_tag :tag => 'a', :content => /Marat/ + assert_tag :tag => 'h3', :content => /Tags/ + assert_tag :tag => 'h3', :content => /Recently viewed/ + # private projects hidden + # assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ + # assert_no_tag :tag => 'a', :content => /Issue on project 2/ + # project column + # assert_tag :tag => 'th', :content => /Project/ + end + + test "should get index deny user in project" do + # log_user('admin', 'admin') + # @request.session[:user_id] = 4 + + get :index, :project_id => 1 + assert_response :redirect + # assert_tag :tag => 'div', :attributes => { :id => "login-form"} + # assert_select 'div#login-form' + end + + test "should get index with filters" do + @request.session[:user_id] = 1 + get :index, :is_company => ActiveRecord::Base.connection.quoted_true.gsub(/'/, '') + assert_response :success + assert_template :index + assert_select 'div#content div#contact_list table.contacts td.name h1 a', 'Domoway' + end + + + test "should get show" do + # log_user('admin', 'admin') + @request.session[:user_id] = 2 + Setting.default_language = 'en' + + get :show, :id => 3, :project_id => 1 + assert_response :success + assert_template :show + assert_not_nil assigns(:contact) + assert_not_nil assigns(:project) + assert_tag :tag => 'h1', :content => /Domoway/ + + assert_select 'div#tags_data span.tag a', 'main' + assert_select 'div#tags_data span.tag a', 'test' + + assert_select 'div#employee h4.contacts_header a', /Marat Aminov/ + assert_select 'div#employee h4.contacts_header a', /Ivan Ivanov/ + + assert_select 'div#comments div#notes table.note_data td.name h4', 4 + + assert_select 'h3', "Recently viewed" + + # assert_select 'div#deals h3', "Deals - $15,000.00", "Sum should be 15,000.00" + assert_select 'div#deals a', "Delevelop redmine plugin" + assert_select 'div#deals a', "Second deal with contacts" + + end + + test "should get show without deals" do + # log_user('admin', 'admin') + @request.session[:user_id] = 4 + Setting.default_language = 'en' + + get :show, :id => 3, :project_id => 1 + assert_response :success + assert_template :show + assert_not_nil assigns(:contact) + assert_not_nil assigns(:project) + assert_tag :tag => 'h1', :content => /Domoway/ + + assert_select 'div#tags_data span.tag a', 'main' + assert_select 'div#tags_data span.tag a', 'test' + + assert_select 'div#employee h4.contacts_header a', /Marat Aminov/ + assert_select 'div#employee h4.contacts_header a', /Ivan Ivanov/ + + assert_select 'div#comments div#notes table.note_data td.name h4', 4 + + assert_select 'h3', "Recently viewed" + + assert_select 'div#deals a', {:count => 0, :text => /Delevelop redmine plugin/} + assert_select 'div#deals a', {:count => 0, :text => /Second deal with contacts/} + + end + + test "should get new" do + @request.session[:user_id] = 2 + get :new, :project_id => 1 + assert_response :success + assert_template 'new' + assert_select 'input#contact_first_name' + end + + test "should not get new by deny user" do + @request.session[:user_id] = 4 + get :new, :project_id => 1 + assert_response :forbidden + end + + test "should post create" do + @request.session[:user_id] = 1 + assert_difference 'Contact.count' do + post :create, :project_id => 1, + :contact => { + :company => "OOO \"GKR\"", + :is_company => 0, + :job_title => "CFO", + :assigned_to_id => 3, + :tag_list => "test,new", + :last_name => "New", + :middle_name => "Ivanovich", + :first_name => "Created"} + + end + assert_redirected_to :controller => 'contacts', :action => 'show', :id => Contact.last.id, :project_id => Contact.last.project + + contact = Contact.find(:first, :conditions => {:first_name => "Created", :last_name => "New", :middle_name => "Ivanovich"}) + assert_not_nil contact + assert_equal "CFO", contact.job_title + assert_equal ["new", "test"], contact.tag_list.sort + assert_equal 3, contact.assigned_to_id + end + + test "should not post create by deny user" do + @request.session[:user_id] = 4 + post :create, :project_id => 1, + :contact => { + :company => "OOO \"GKR\"", + :is_company => 0, + :job_title => "CFO", + :assigned_to_id => 3, + :tag_list => "test,new", + :last_name => "New", + :middle_name => "Ivanovich", + :first_name => "Created"} + assert_response :forbidden + end + + test "should get edit" do + @request.session[:user_id] = 1 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_not_nil assigns(:contact) + assert_equal Contact.find(1), assigns(:contact) + end + + test "should put update" do + @request.session[:user_id] = 1 + + contact = Contact.find(1) + old_firstname = contact.first_name + new_firstname = 'Fist name modified by ContactsControllerTest#test_put_update' + + put :update, :id => 1, :project_id => 1, :contact => {:first_name => new_firstname} + assert_redirected_to :action => 'show', :id => '1', :project_id => contact.project.id + contact.reload + assert_equal new_firstname, contact.first_name + + end + + test "should post destroy" do + @request.session[:user_id] = 1 + post :destroy, :id => 1, :project_id => 'ecookbook' + assert_redirected_to :action => 'index', :project_id => 'ecookbook' + assert_nil Contact.find_by_id(1) + end + + test "should post edit tags" do + @request.session[:user_id] = 1 + + post :edit_tags, :id => 1, :project_id => 'ecookbook', :contact => {:tag_list => "main,test,new" } + assert_redirected_to :controller => 'contacts', :action => 'show', :id => 1, :project_id => 'ecookbook' + + contact = Contact.find(1) + assert_equal ["main", "new", "test"], contact.tag_list.sort + end + + test "should not post edit tags by deny user" do + @request.session[:user_id] = 4 + + post :edit_tags, :id => 1, :project_id => 'ecookbook', :contact => {:tag_list => "main,test,new" } + assert_response :forbidden + end + + test "should bulk destroy contacts" do + @request.session[:user_id] = 1 + + post :bulk_destroy, :ids => [1, 2, 3] + assert_redirected_to :controller => 'contacts', :action => 'index' + + assert_nil Contact.find_by_id(1, 2, 3) + end + + test "should not bulk destroy contacts by deny user" do + @request.session[:user_id] = 4 + assert_raises ActiveRecord::RecordNotFound do + post :bulk_destroy, :ids => [1, 2] + end + + end + + test "should bulk edit mails" do + @request.session[:user_id] = 1 + post :edit_mails, :ids => [1, 2] + assert_response :success + assert_template 'edit_mails' + assert_not_nil assigns(:contacts) + end + + test "should not bulk edit mails by deny user" do + @request.session[:user_id] = 4 + post :edit_mails, :ids => [1, 2] + assert_response 403 + end + + test "should not bulk send mails by deny user" do + @request.session[:user_id] = 4 + post :send_mails, :ids => [1, 2], :message => "test message", :subject => "test subject" + assert_response 403 + end + + test "should bulk send mails" do + @request.session[:user_id] = 1 + post :send_mails, :ids => [1, 2], :from => "test@mail.from", :bcc => "test@mail.bcc", :"message-content" => "Hello %%NAME%%\ntest message", :subject => "test subject" + mail = ActionMailer::Base.deliveries.last + note = Contact.find(2).notes.find_by_subject("test subject") + + assert_not_nil mail + assert mail.body.include?('Hello Marat') + assert_equal "test subject", mail.subject + assert_equal "test@mail.from", mail.from.first + assert_equal "test@mail.bcc", mail.bcc.first + assert_not_nil note + assert_equal note.type_id, Note.note_types[:email] + assert_equal "test subject", note.subject + assert_equal "Hello Marat\ntest message", note.content + assert_equal "Hello Ivan\ntest message", Contact.find(1).notes.find_by_subject("test subject").content + end + + test "should bulk edit contacts" do + @request.session[:user_id] = 1 + post :bulk_edit, :ids => [1, 2] + assert_response :success + assert_template 'bulk_edit' + assert_not_nil assigns(:contacts) + end + + test "should not bulk edit contacts by deny user" do + @request.session[:user_id] = 4 + assert_raises ActiveRecord::RecordNotFound do + post :bulk_edit, :ids => [1, 2] + end + end + + test "should put bulk update " do + @request.session[:user_id] = 1 + + put :bulk_update, :ids => [1, 2], + :add_tag_list => 'bulk, edit, tags', + :delete_tag_list => 'main', + :add_projects_list => ['1', '2', '3'], + :delete_projects_list => ['3', '4', '5'], + :note => {:content => "Bulk note content"}, + :contact => {:company => "Bulk company", :job_title => ''} + + assert_redirected_to :controller => 'contacts', :action => 'index', :project_id => nil + contacts = Contact.find([1, 2]) + contacts.each do |contact| + assert_equal "Bulk company", contact.company + assert [], (contact.tag_list & ['bulk', 'edit', 'tags']) - ['bulk', 'edit', 'tags'] + assert contact.tag_list.include?('bulk') + assert contact.tag_list.include?('edit') + assert contact.tag_list.include?('tags') + assert !contact.tag_list.include?('main') + assert_equal [], contact.project_ids - [1, 2] + + assert_equal "Bulk note content", contact.notes.find_by_content("Bulk note content").content + end + + end + + test "should not put bulk update by deny user" do + @request.session[:user_id] = 4 + + assert_raises ActiveRecord::RecordNotFound do + put :bulk_update, :ids => [1, 2], + :add_tag_list => 'bulk, edit, tags', + :delete_tag_list => 'main', + :note => {:content => "Bulk note content"}, + :contact => {:company => "Bulk company", :job_title => ''} + end + end + + test "should get contacts notes" do + # log_user('admin', 'admin') + @request.session[:user_id] = 2 + Setting.default_language = 'en' + + get :contacts_notes + assert_response :success + assert_template :contacts_notes + assert_select 'h2', /All notes/ + # assert_select 'table.note_data h4.contacts_header', 4 + # assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ + # assert_no_tag :tag => 'a', :content => /Issue on project 2/ + # project column + # assert_tag :tag => 'th', :content => /Project/ + end + + test "should get context menu" do + @request.session[:user_id] = 1 + xhr :get, :context_menu, :back_url => "/projects/contacts-plugin/contacts", :project_id => 'ecookbook', :ids => ['1', '2'] + assert_response :success + assert_template 'context_menu' + end + + test "should post index live search" do + @request.session[:user_id] = 1 + xhr :post, :index, :search => "Domoway", :project_id => 'ecookbook' + assert_response :success + assert_template '_list' + assert_tag :tag => 'a', :content => /Domoway/ + end + + +end diff --git a/test/functional/contacts_duplicates_controller_test.rb b/test/functional/contacts_duplicates_controller_test.rb new file mode 100644 index 0000000..42d894d --- /dev/null +++ b/test/functional/contacts_duplicates_controller_test.rb @@ -0,0 +1,58 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'contacts_duplicates_controller' + +class ContactsDuplicatesControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + # TODO: Test for delete tags in update action + + def setup + RedmineContacts::TestCase.prepare + + @controller = ContactsDuplicatesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + end + + test "should merge duplicates" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + Setting.default_language = 'en' + + get :merge, :project_id => 1, :contact_id => 1, :dublicate_id => 2 + assert_redirected_to :controller => "contacts", :action => 'show', :id => 2, :project_id => 'ecookbook' + + contact = Contact.find(2) + assert_equal contact.emails, ["marat@mail.ru", "marat@mail.com", "ivan@mail.com"] + end + +end \ No newline at end of file diff --git a/test/functional/contacts_mailer_controller_test.rb b/test/functional/contacts_mailer_controller_test.rb new file mode 100644 index 0000000..099be54 --- /dev/null +++ b/test/functional/contacts_mailer_controller_test.rb @@ -0,0 +1,53 @@ +# redMine - project management software +# Copyright (C) 2006-2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) +require 'contacts_mailer_controller' + +# Re-raise errors caught by the controller. +class ContactsMailerController; def rescue_action(e) raise e end; end + +class ContactsMailerControllerTest < ActionController::TestCase + fixtures :users, :projects, :enabled_modules, :roles, :members, :member_roles, :issues, :issue_statuses, :trackers, :enumerations, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/contacts_mailer' + + def setup + RedmineContacts::TestCase.prepare + + @controller = ContactsMailerController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + end + + def test_should_create_issue + # Enable API and set a key + Setting.mail_handler_api_enabled = 1 + Setting.mail_handler_api_key = 'secret' + + post :index, :key => 'secret', :email => IO.read(File.join(FIXTURES_PATH, 'fwd_new_note_plain.eml')) + assert_response 201 + end + +end diff --git a/test/functional/contacts_projects_controller_test.rb b/test/functional/contacts_projects_controller_test.rb new file mode 100644 index 0000000..c6866ee --- /dev/null +++ b/test/functional/contacts_projects_controller_test.rb @@ -0,0 +1,72 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'contacts_projects_controller' + +class ContactsProjectsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :versions, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :time_entries, + :contacts, + :contacts_projects, + :deals, + :notes + + def setup + RedmineContacts::TestCase.prepare + @controller = ContactsProjectsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + end + + test "should delete project" do + @request.session[:user_id] = 1 + contact = Contact.find(1) + assert RedmineContacts::TestCase.is_arrays_equal(contact.project_ids, [1, 2]) + # assert_equal '12', "#{contact.project_ids} || #{contact.projects.map(&:name).join(', ')} #{Project.find(1).contacts.map(&:name).join(', ')}, #{Project.find(2).name}" + xhr :post, :delete, :project_id => 1, :disconnect_project_id => 2, :contact_id => 1 + assert_response :success + assert_select_rjs :replace_html, 'contact_projects' + + contact.reload + assert_equal [1], contact.project_ids + end + + test "should not delete last project" do + @request.session[:user_id] = 1 + contact = Contact.find(1) + assert RedmineContacts::TestCase.is_arrays_equal(contact.project_ids, [1, 2]) + # assert_equal '12', "#{contact.project_ids} || #{contact.projects.map(&:name).join(', ')} #{Project.find(1).contacts.map(&:name).join(', ')}, #{Project.find(2).name}" + xhr :post, :delete, :project_id => 1, :disconnect_project_id => 2, :contact_id => 1 + assert_response :success + xhr :post, :delete, :project_id => 1, :disconnect_project_id => 1, :contact_id => 1 + assert_response 403 + + contact.reload + assert_equal [1], contact.project_ids + end + + test "should add project" do + @request.session[:user_id] = 1 + + xhr :post, :add, :project_id => "ecookbook", :new_project_id => 2, :contact_id => 2 + assert_response :success + assert_select_rjs :replace_html, 'contact_projects' + contact = Contact.find(2) + assert RedmineContacts::TestCase.is_arrays_equal(contact.project_ids, [1, 2]) + end + + + + + +end diff --git a/test/functional/contacts_settings_controller_test.rb b/test/functional/contacts_settings_controller_test.rb new file mode 100644 index 0000000..7ae1529 --- /dev/null +++ b/test/functional/contacts_settings_controller_test.rb @@ -0,0 +1,39 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'contacts_settings_controller' + +class ContactsSettingsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :versions, + :trackers, + :projects_trackers, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :time_entries, + :contacts, + :contacts_projects, + :deals, + :notes + + def setup + RedmineContacts::TestCase.prepare + @controller = ContactsSettingsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + end + + test "should save setting" do + @request.session[:user_id] = 1 + post :save, :project_id => 1, :contacts_settings => {:setting1 => 1, :setting2 => "Hello"} + assert_redirected_to :controller => 'projects', :action => 'settings', :tab => 'contacts', :id => "ecookbook" + assert_equal 1, ContactsSetting[:setting1, 1] + assert_equal "Hello", ContactsSetting[:setting2, 1] + end + +end diff --git a/test/functional/contacts_tags_controller_test.rb b/test/functional/contacts_tags_controller_test.rb new file mode 100644 index 0000000..d1a897d --- /dev/null +++ b/test/functional/contacts_tags_controller_test.rb @@ -0,0 +1,85 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'contacts_tags_controller' + +class ContactsTagsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :tags, + :taggings + + def setup + RedmineContacts::TestCase.prepare + + @controller = ContactsTagsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + + @request.env['HTTP_REFERER'] = '/' + end + + # + # test "should get index" do + # # log_user('admin', 'admin') + # @request.session[:user_id] = 1 + # + # get "/settings/plugin/contacts", :tab => "tags" + # assert_response :success + # assert_not_nil assigns(:tags) + # assert_select 'div#tab-content-tags table td a', "main" + # assert_select 'div#tab-content-tags table td a', "test" + # end + + test "should get edit" do + @request.session[:user_id] = 1 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_not_nil assigns(:tag) + assert_equal ActsAsTaggableOn::Tag.find(1), assigns(:tag) + end + + test "should post update" do + @request.session[:user_id] = 1 + tag1 = ActsAsTaggableOn::Tag.find(1) + old_name = tag1.name + new_name = "updated main" + post :update, :id => 1, :tag => {:name => new_name, :color_name=>"#000000"} + assert_redirected_to :controller => 'settings', :action => 'plugin', :id => "contacts", :tab => "tags" + tag1.reload + assert_equal new_name, tag1.name + end + + test "should post destroy" do + @request.session[:user_id] = 1 + assert_difference 'ActsAsTaggableOn::Tag.count', -1 do + post :destroy, :id => 1 + assert_response 302 + end + end + +end diff --git a/test/functional/contacts_tasks_controller_test.rb b/test/functional/contacts_tasks_controller_test.rb new file mode 100644 index 0000000..32ff7f2 --- /dev/null +++ b/test/functional/contacts_tasks_controller_test.rb @@ -0,0 +1,85 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'contacts_tasks_controller' + +class ContactsTasksControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes, + :contacts_issues, + :tags, + :taggings + + def setup + RedmineContacts::TestCase.prepare + + @controller = ContactsTasksController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + + + end + + test "should get index" do + @request.session[:user_id] = 1 + + get :index # TODO: DEPRECATION WARNING + assert_response :success + assert_template :index + assert_not_nil assigns(:contacts_issues) + end + + test "should get index in project" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + Setting.default_language = 'en' + + get :index, :project_id => 1 + assert_response :success + assert_template :index + assert_not_nil assigns(:contacts_issues) + assert_not_nil assigns(:project) + assert_select 'tr#issue_1 td a', "Can't print recipes" + + + # private projects hidden + # assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ + # assert_no_tag :tag => 'a', :content => /Issue on project 2/ + # project column + # assert_tag :tag => 'th', :content => /Project/ + end + + test "should get new" do + @request.session[:user_id] = 1 + @request.env['HTTP_REFERER'] = 'http://localhost:3000/contacts/1' + get :new, :project_id => 1, :contact_id => 1, :task_subject => "test subject", :task_tracker => 1, :due_date => Date.to_s, :assigned_to => 1, :task_description => "Test task descripiton" + assert_response 302 + # assert_not_nil Issue.find_by_subject("test subject") + end + + +end diff --git a/test/functional/deals_controller_test.rb b/test/functional/deals_controller_test.rb new file mode 100644 index 0000000..7e8ea78 --- /dev/null +++ b/test/functional/deals_controller_test.rb @@ -0,0 +1,244 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'deals_controller' + +class DealsControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes + # Replace this with your real tests. + + def setup + RedmineContacts::TestCase.prepare + Setting.default_language = 'en' + + end + + test "should get index" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + + + get :index + assert_response :success + assert_template :index + assert_not_nil assigns(:deals) + assert_nil assigns(:project) + assert_select 'a', /First deal with contacts/ + # private projects hidden + # assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ + # assert_no_tag :tag => 'a', :content => /Issue on project 2/ + # project column + # assert_tag :tag => 'th', :content => /Project/ + end + + test "should not get closed in index" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + + + get :index + assert_response :success + assert_template :index + assert_select 'a', /First deal with contacts/ + assert_select 'table.contacts.index h1.deal_name a', {:count => 0, :text => /Closed deal/} + + end + + test "should get closed index with pages" do + # log_user('admin', 'admin') + @request.session[:user_id] = 1 + + + get :index, :page => 2, :page_size => 1, :status_id => "" + assert_response :success + assert_template :index + assert_select 'table.contacts.index h1.deal_name a', /Deal without contact/ + assert_select 'table.contacts.index h1.deal_name a', {:count => 0, :text => /First deal with contacts/} + + end + + + test "should get index with filters" do + @request.session[:user_id] = 1 + + get :index + assert_response :success + assert_template :index + assert_not_nil assigns(:deals) + assert_nil assigns(:project) + + xhr :post, :index, :status_id => "" + assert_select 'table.contacts.index h1.deal_name a', /First deal with contacts/ + assert_select 'table.contacts.index h1.deal_name a', /Second deal with contacts/ + assert_select 'table.contacts.index h1.deal_name a', /Delevelop redmine plugin/ + + xhr :post, :index, :status_id => "2" + # assert_select 'a', /First deal with contacts/, 0 + # assert_select 'a', /Second deal with contacts/, 0 + assert_select 'table.contacts.index h1.deal_name a', /Delevelop redmine plugin/ + assert_select 'table.contacts.index h1.deal_name a', {:count => 0, :text => "Second deal with contacts"} + + xhr :post, :index, :assigned_to_id => "1" + # assert_select 'a', /First deal with contacts/, 0 + # assert_select 'a', /Second deal with contacts/, 0 + assert_select 'table.contacts.index h1.deal_name a', /First deal with contacts/ + + end + + test "should get index with project" do + # log_user('admin', 'admin') + @request.session[:user_id] = 3 + + get :index, :project_id => 1 + assert_response :success + assert_template :index + assert_not_nil assigns(:deals) + assert_not_nil assigns(:project) + assert_tag :tag => 'a', :content => /First deal with contacts/ + assert_no_tag :tag => 'a', :content => /Second deal with contacts/ + assert_tag :tag => 'h3', :content => /Recently viewed/ + # private projects hidden + # assert_no_tag :tag => 'a', :content => /Issue of a private subproject/ + # assert_no_tag :tag => 'a', :content => /Issue on project 2/ + # project column + # assert_tag :tag => 'th', :content => /Project/ + end + + test "should post create" do + @request.session[:user_id] = 1 + assert_difference 'Deal.count' do + post :create, :project_id => 1, + :deal => {:price => 5500, + :name => "New created deal 1", + :background =>"Background of new created deal", + :contact_id => 2, + :assigned_to_id => 3, + :category_id => 1} + + end + assert_redirected_to :controller => 'deals', :action => 'show', :id => Deal.last.id + + deal = Deal.find_by_name('New created deal 1') + assert_not_nil deal + assert_equal 1, deal.category_id + assert_equal 2, deal.contact_id + assert_equal 3, deal.assigned_to_id + end + + test "should get show" do + @request.session[:user_id] = 1 + get :show, :id => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:deal) + assert_equal Deal.find(1), assigns(:deal) + end + + test "should get show with statuses" do + project = Project.find(1) + project.deal_statuses << DealStatus.find(1) + project.deal_statuses << DealStatus.find(2) + project.save + + assert_equal ['Pending', 'Won', 'Lost'], DealStatus.all.map(&:name) + assert_equal ['Pending', 'Won'], project.deal_statuses.map(&:name) + @request.session[:user_id] = 1 + get :show, :id => 1 + assert_response :success + assert_template 'show' + assert_select '#deal_status_id', /Pending/ + assert_select '#deal_status_id', /Won/ + assert_select '#deal_status_id', {:count => 0, :text => /Lost/} + + end + + test "should get edit" do + @request.session[:user_id] = 1 + get :edit, :id => 1 + assert_response :success + assert_template 'edit' + assert_not_nil assigns(:deal) + assert_equal Deal.find(1), assigns(:deal) + end + + test "should put update" do + @request.session[:user_id] = 1 + + deal = Deal.find(1) + old_name = deal.name + new_name = 'Name modified by DealControllerTest#test_put_update' + + put :update, :id => 1, :deal => {:name => new_name, :currency => 2, :price => 23000} + assert_redirected_to :action => 'show', :id => '1' + deal.reload + assert_equal 23000, deal.price + + get :show, :id => 1 + assert_response :success + assert_select 'td.subject_info', /£23 000.00/ + + assert_equal new_name, deal.name + end + + test "should bulk edit deals" do + @request.session[:user_id] = 1 + post :bulk_edit, :ids => [1, 2, 4] + assert_response :success + assert_template 'bulk_edit' + assert_not_nil assigns(:deals) + end + + test "should not bulk edit deals by deny user" do + @request.session[:user_id] = 4 + post :bulk_edit, :ids => [1, 2, 4] + assert_response 403 + end + + test "should put bulk update " do + @request.session[:user_id] = 1 + + put :bulk_update, :ids => [1, 2, 4], + :deal => {:assigned_to_id => 2, + :category_id => 2, + :currency => 1}, + :note => {:content => "Bulk deals edit note content"} + + assert_redirected_to :controller => 'deals', :action => 'index', :project_id => nil + + deals = Deal.find(1, 2, 4) + + assert_equal [2], deals.collect(&:assigned_to_id).uniq + assert_equal [2], deals.collect(&:category_id).uniq + assert_equal [1], deals.collect(&:currency).uniq + + assert_equal 3, Note.count(:conditions => {:content => "Bulk deals edit note content"}) + end + + + + +end diff --git a/test/functional/notes_controller_test.rb b/test/functional/notes_controller_test.rb new file mode 100644 index 0000000..9bf55c9 --- /dev/null +++ b/test/functional/notes_controller_test.rb @@ -0,0 +1,77 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'notes_controller' + +class NotesControllerTest < ActionController::TestCase + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + def setup + RedmineContacts::TestCase.prepare + + @controller = NotesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + User.current = nil + @request.env['HTTP_REFERER'] = '/' + end + + test "should post add note to contact" do + @request.session[:user_id] = 1 + assert_difference 'Note.count' do + post :add_note, :project_id => 1, + :note => { + :subject => "Note subject", + :content => "Note *content*"}, + :source_type => Contact.to_s, + :source_id => 1 + + end + + note = Note.find(:first, :conditions => {:subject => "Note subject", :content => "Note *content*"}) + assert_not_nil note + assert_equal 1, note.source_id + assert_equal Contact, note.source.class + end + + test "should put update" do + @request.session[:user_id] = 1 + + note = Note.find(1) + old_content = note.content + new_content = 'New note content' + + put :update, :note_id => 1, :project_id => 1, :note => {:content => new_content} + assert_redirected_to :action => 'show', :project_id => note.source.project, :note_id => note.id + note.reload + assert_equal new_content, note.content + + end + +end diff --git a/test/integration/api_test/contact.xml b/test/integration/api_test/contact.xml new file mode 100644 index 0000000..b0848d3 --- /dev/null +++ b/test/integration/api_test/contact.xml @@ -0,0 +1,6 @@ + + + API contact name + true + contacts-plugin + \ No newline at end of file diff --git a/test/integration/api_test/contacts_test.rb b/test/integration/api_test/contacts_test.rb new file mode 100644 index 0000000..e4a795b --- /dev/null +++ b/test/integration/api_test/contacts_test.rb @@ -0,0 +1,98 @@ +# Redmine - project management software +# Copyright (C) 2006-2010 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../../test_helper' +# require File.dirname(__FILE__) + '/../../../../../test/test_helper' + +class ApiTest::ContactsTest < ActionController::IntegrationTest + fixtures :all, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + def setup + Setting.rest_api_enabled = '1' + RedmineContacts::TestCase.prepare + end + + test "GET /contacts.xml" do + # Use a private project to make sure auth is really working and not just + # only showing public issues. + ActiveSupport::TestCase.should_allow_api_authentication(:get, "/projects/private-child/contacts.xml") + # test "should contain metadata" do + get '/contacts.xml', {}, :authorization => credentials('admin') + + assert_tag :tag => 'contacts', + :attributes => { + :type => 'array', + :total_count => assigns(:contacts_count), + :limit => 20, + :offset => 0 + } + # end + + end + + # Issue 6 is on a private project + # context "/contacts/2.xml" do + # should_allow_api_authentication(:get, "/contacts/2.xml") + # end + + test "POST /contacts.xml" do + ActiveSupport::TestCase.should_allow_api_authentication(:post, + '/contacts.xml', + {:contact => {:project_id => 1, :first_name => 'API test'}}, + {:success_code => :created}) + + assert_difference('Contact.count') do + post '/contacts.xml', {:contact => {:project_id => 1, :first_name => 'API test'}}, :authorization => credentials('admin') + end + + contact = Contact.first(:order => 'id DESC') + assert_equal 'API test', contact.first_name + + assert_response :created + assert_equal 'application/xml', @response.content_type + assert_tag 'contact', :child => {:tag => 'id', :content => contact.id.to_s} + end + + # Issue 6 is on a private project + test "PUT /contacts/1.xml" do + @parameters = {:contact => {:first_name => 'API update'}} + @headers = { :authorization => credentials('admin') } + + ActiveSupport::TestCase.should_allow_api_authentication(:put, + '/contacts/1.xml', + {:contact => {:first_name => 'API update'}}, + {:success_code => :ok}) + + assert_no_difference('Contact.count') do + put '/contacts/1.xml', @parameters, @headers + end + + contact = Contact.find(1) + assert_equal "API update", contact.first_name + + end + + def credentials(user, password=nil) + ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user) + end +end diff --git a/test/integration/common_views_test.rb b/test/integration/common_views_test.rb new file mode 100644 index 0000000..6ae312d --- /dev/null +++ b/test/integration/common_views_test.rb @@ -0,0 +1,72 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' +require File.dirname(__FILE__) + '/../../../../../test/test_helper' + +class DeleteTagTest < ActionController::IntegrationTest + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + def setup + RedmineContacts::TestCase.prepare + + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @request.env['HTTP_REFERER'] = '/' + end + + test "View user" do + log_user("rhill", "foo") + get "/users/2" + assert_response :success + end + + test "View contacts activity" do + log_user("admin", "admin") + get "/projects/ecookbook/activity?show_contacts=1" + assert_response :success + end + +end diff --git a/test/integration/delete_tag_test.rb b/test/integration/delete_tag_test.rb new file mode 100644 index 0000000..8a42b39 --- /dev/null +++ b/test/integration/delete_tag_test.rb @@ -0,0 +1,82 @@ +# redMine - project management software +# Copyright (C) 2006-2007 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.dirname(__FILE__) + '/../test_helper' +require File.dirname(__FILE__) + '/../../../../../test/test_helper' + +class DeleteTagTest < ActionController::IntegrationTest + fixtures :projects, + :users, + :roles, + :members, + :member_roles, + :issues, + :issue_statuses, + :versions, + :trackers, + :projects_trackers, + :issue_categories, + :enabled_modules, + :enumerations, + :attachments, + :workflows, + :custom_fields, + :custom_values, + :custom_fields_projects, + :custom_fields_trackers, + :time_entries, + :journals, + :journal_details, + :queries, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + def setup + RedmineContacts::TestCase.prepare + + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @request.env['HTTP_REFERER'] = '/' + end + + test "Contacts with deleted tags should opens" do + log_user("admin", "admin") + + assert_not_nil User.first + + get "contacts" + assert_response :success + + get "contacts/1" + assert_response :success + + get "settings/plugin/contacts" + # assert_select 'div#tab-content-tags table td a', "main" + + assert_response :success + delete "contacts_tags/destroy/2" + # assert_redirected_to "/settings/plugin/contacts" + assert nil == ActsAsTaggableOn::Tag.find_by_id(2) + + # get "contacts/3" + # assert_response :success + end +end diff --git a/test/integration/routing_test.rb b/test/integration/routing_test.rb new file mode 100644 index 0000000..aad097c --- /dev/null +++ b/test/integration/routing_test.rb @@ -0,0 +1,83 @@ +# redMine - project management software +# Copyright (C) 2006-2010 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require File.expand_path('../../test_helper', __FILE__) + +class RoutingTest < ActionController::IntegrationTest + + test "contacts" do + # REST actions + assert_routing({ :path => "/contacts", :method => :get }, { :controller => "contacts", :action => "index" }) + assert_routing({ :path => "/contacts.xml", :method => :get }, { :controller => "contacts", :action => "index", :format => 'xml' }) + assert_routing({ :path => "/contacts.atom", :method => :get }, { :controller => "contacts", :action => "index", :format => 'atom' }) + assert_routing({ :path => "/contacts/notes", :method => :get }, { :controller => "contacts", :action => "contacts_notes" }) + assert_routing({ :path => "/contacts/1", :method => :get }, { :controller => "contacts", :action => "show", :id => '1'}) + assert_routing({ :path => "/contacts/1/edit", :method => :get }, { :controller => "contacts", :action => "edit", :id => '1'}) + assert_routing({ :path => "/projects/23/contacts", :method => :get }, { :controller => "contacts", :action => "index", :project_id => '23'}) + assert_routing({ :path => "/projects/23/contacts.xml", :method => :get }, { :controller => "contacts", :action => "index", :project_id => '23', :format => 'xml'}) + assert_routing({ :path => "/projects/23/contacts.atom", :method => :get }, { :controller => "contacts", :action => "index", :project_id => '23', :format => 'atom'}) + assert_routing({ :path => "/projects/23/contacts/notes", :method => :get }, { :controller => "contacts", :action => "contacts_notes", :project_id => '23'}) + + assert_routing({ :path => "/projects/23/contacts/2/edit_tags", :method => :post }, { :controller => "contacts", :action => "edit_tags", :project_id => '23', :id => '2' }) + assert_routing({ :path => "/contacts.xml", :method => :post }, { :controller => "contacts", :action => "create", :format => 'xml' }) + + assert_routing({ :path => "/contacts/1.xml", :method => :put }, { :controller => "contacts", :action => "update", :format => 'xml', :id => "1" }) + + # should_route :get, "/contacts.atom", :controller => 'contacts', :action => 'index', :format => 'atom' + # should_route :get, "/contacts.xml", :controller => 'contacts', :action => 'index', :format => 'xml' + # should_route :get, "/projects/23/contacts", :controller => 'contacts', :action => 'index', :project_id => '23' + # should_route :get, "/projects/23/contacts.atom", :controller => 'contacts', :action => 'index', :project_id => '23', :format => 'atom' + # should_route :get, "/projects/23/contacts.xml", :controller => 'contacts', :action => 'index', :project_id => '23', :format => 'xml' + # should_route :get, "/contacts/64", :controller => 'contacts', :action => 'show', :id => '64' + # should_route :get, "/contacts/64.atom", :controller => 'contacts', :action => 'show', :id => '64', :format => 'atom' + # should_route :get, "/contacts/64.xml", :controller => 'contacts', :action => 'show', :id => '64', :format => 'xml' + # + # should_route :get, "/projects/23/contacts/new", :controller => 'contacts', :action => 'new', :project_id => '23' + # should_route :post, "/projects/23/contacts", :controller => 'contacts', :action => 'create', :project_id => '23' + # should_route :post, "/contacts.xml", :controller => 'contacts', :action => 'create', :format => 'xml' + # + # should_route :get, "/contacts/64/edit", :controller => 'contacts', :action => 'edit', :id => '64' + # # TODO: Should use PUT + # should_route :post, "/contacts/64/edit", :controller => 'contacts', :action => 'edit', :id => '64' + # should_route :put, "/contacts/1.xml", :controller => 'contacts', :action => 'update', :id => '1', :format => 'xml' + # + # # TODO: Should use DELETE + # should_route :post, "/contacts/64/destroy", :controller => 'contacts', :action => 'destroy', :id => '64' + # should_route :delete, "/contacts/1.xml", :controller => 'contacts', :action => 'destroy', :id => '1', :format => 'xml' + # + # # Extra actions + # should_route :get, "/contacts/bulk_edit", :controller => 'issues', :action => 'bulk_edit' + # should_route :post, "/contacts/bulk_edit", :controller => 'issues', :action => 'bulk_update' + end + + test "deals" do + # REST actions + assert_routing({ :path => "/deals", :method => :get }, { :controller => "deals", :action => "index" }) + # assert_routing({ :path => "/deals.xml", :method => :get }, { :controller => "deals", :action => "index", :format => 'xml' }) + # assert_routing({ :path => "/deals.atom", :method => :get }, { :controller => "deals", :action => "index", :format => 'atom' }) + assert_routing({ :path => "/deals/1", :method => :get }, { :controller => "deals", :action => "show", :id => '1'}) + assert_routing({ :path => "/deals/1/edit", :method => :get }, { :controller => "deals", :action => "edit", :id => '1'}) + assert_routing({ :path => "/projects/23/deals", :method => :get }, { :controller => "deals", :action => "index", :project_id => '23'}) + # assert_routing({ :path => "/projects/23/deals.xml", :method => :get }, { :controller => "deals", :action => "index", :project_id => '23', :format => 'xml'}) + # assert_routing({ :path => "/projects/23/deals.atom", :method => :get }, { :controller => "deals", :action => "index", :project_id => '23', :format => 'atom'}) + # assert_routing({ :path => "/projects/23/deals/notes", :method => :get }, { :controller => "deals", :action => "deals_notes", :project_id => '23'}) + + # assert_routing({ :path => "/deals.xml", :method => :post }, { :controller => "deals", :action => "create", :format => 'xml' }) + # + # assert_routing({ :path => "/deals/1.xml", :method => :put }, { :controller => "deals", :action => "update", :format => 'xml', :id => "1" }) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..56d5c79 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,40 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper') +# require 'redgreen' + +Engines::Testing.set_fixture_path + +class RedmineContacts::TestCase + + def uploaded_test_file(name, mime) + ActionController::TestUploadedFile.new(ActiveSupport::TestCase.fixture_path + "/files/#{name}", mime, true) + end + + def self.is_arrays_equal(a1, a2) + (a1 - a2) - (a2 - a1) == [] + end + + def self.prepare + Role.find(1, 2, 3, 4).each do |r| + r.permissions << :view_contacts + r.save + end + Role.find(1, 2).each do |r| + r.permissions << :edit_contacts + r.save + end + Role.find(1, 2, 3).each do |r| + r.permissions << :view_deals + r.save + end + + Role.find(2) do |r| + r.permissions << :edit_deals + r.save + end + + Project.find(1, 2, 3, 4, 5).each do |project| + EnabledModule.create(:project => project, :name => 'contacts_module') + end + end + +end \ No newline at end of file diff --git a/test/unit/contact_test.rb b/test/unit/contact_test.rb new file mode 100644 index 0000000..7c20a5f --- /dev/null +++ b/test/unit/contact_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ContactTest < ActiveSupport::TestCase + fixtures :contacts + + # Replace this with your real tests. + test "Should get first by email" do + emails = ["marat@mail.ru", "domoway.mail.ru"] + assert_equal 2, Contact.find_by_emails(emails).count + end + + test "Should get first by second email" do + emails = ["marat@mail.com"] + assert_equal 1, Contact.find_by_emails(emails).count + end + + +end diff --git a/test/unit/contacts_issues_test.rb b/test/unit/contacts_issues_test.rb new file mode 100644 index 0000000..f67bd42 --- /dev/null +++ b/test/unit/contacts_issues_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ContactsIssuesTest < ActiveSupport::TestCase + fixtures :contacts_issues + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/contacts_mailer_test.rb b/test/unit/contacts_mailer_test.rb new file mode 100644 index 0000000..d686e67 --- /dev/null +++ b/test/unit/contacts_mailer_test.rb @@ -0,0 +1,130 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ContactsMailerTest < ActiveSupport::TestCase + fixtures :users, :projects, + :enabled_modules, + :roles, + :members, + :member_roles, + :users, + :issues, + :issue_statuses, + :workflows, + :trackers, + :projects_trackers, + :versions, + :enumerations, + :issue_categories, + :custom_fields, + :custom_fields_trackers, + :custom_fields_projects, + :boards, + :messages, + :contacts, + :contacts_projects, + :deals, + :notes, + :tags, + :taggings + + FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures/contacts_mailer' + + def setup + RedmineContacts::TestCase.prepare + + ActionMailer::Base.deliveries.clear + Setting.notified_events = Redmine::Notifiable.all.collect(&:name) + end + + + test "Should add contact note from to" do + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + note = submit_email('new_note.eml').first + assert note.is_a?(Note) + assert !note.new_record? + note.reload + assert_equal Contact, note.source.class + assert_equal "New note from email", note.subject + assert_equal User.find_by_login('admin'), note.author + assert_equal Contact.find(1).id, note.source_id + end + + test "Should add contact note from ID in to" do + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + note = submit_email('new_note_by_id.eml').first + assert note.is_a?(Note) + assert !note.new_record? + note.reload + assert_equal Contact, note.source.class + assert_equal "New note from email", note.subject + assert_equal User.find_by_login('admin'), note.author + assert_equal Contact.find(1).id, note.source_id + end + + test "Should add deal note from ID in to" do + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + note = submit_email('new_deal_note_by_id.eml').first + assert note.is_a?(Note) + assert !note.new_record? + note.reload + assert_equal Deal, note.source.class + assert_equal "New note from email", note.subject + assert_equal User.find_by_login('admin'), note.author + assert_equal Deal.find(1).id, note.source_id + end + + + test "Should add contact note from forwarded" do + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + note = submit_email('fwd_new_note_plain.eml').first + assert note.is_a?(Note) + assert !note.new_record? + note.reload + assert_equal Contact, note.source.class + assert_equal "New note from forwarded email", note.subject + assert_equal "From: \"Marat Aminov\" marat@mail.ru\n", note.content.lines.collect[1] + assert_equal User.find_by_login('admin'), note.author + assert_equal Contact.find(2).id, note.source_id + end + + test "Should add contact note from forwarded html" do + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + note = submit_email('fwd_new_note_html.eml').first + assert note.is_a?(Note) + assert !note.new_record? + note.reload + assert_equal Contact, note.source.class + assert_equal "New note from forwarded html email", note.subject + assert_equal "From: Marat Aminov \n", note.content.collect[2] + assert_equal User.find_by_login('admin'), note.author + assert_equal Contact.find(2).id, note.source_id + end + + + test "Should not add contact note from deny user to" do + ActionMailer::Base.deliveries.clear + # This email contains: 'Project: onlinestore' + assert !submit_email('new_deny_note.eml') + # assert note.is_a?(Note) + # assert !note.new_record? + # note.reload + # assert_equal Contact, note.source.class + # assert_equal "New note from email", note.subject + # assert_equal User.find_by_login('admin'), note.author + # assert_equal Contact.find(1).id, note.source_id + end + + + private + + def submit_email(filename, options={}) + raw = IO.read(File.join(FIXTURES_PATH, filename)) + ContactsMailer.receive(raw, options) + end + +end diff --git a/test/unit/contacts_setting_test.rb b/test/unit/contacts_setting_test.rb new file mode 100644 index 0000000..ff726f0 --- /dev/null +++ b/test/unit/contacts_setting_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ContactsSettingTest < ActiveSupport::TestCase + fixtures :contacts_settings + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/deal_category_test.rb b/test/unit/deal_category_test.rb new file mode 100644 index 0000000..c9aac50 --- /dev/null +++ b/test/unit/deal_category_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DealCategoryTest < ActiveSupport::TestCase + fixtures :deal_categories + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/deal_process_test.rb b/test/unit/deal_process_test.rb new file mode 100644 index 0000000..566b7ad --- /dev/null +++ b/test/unit/deal_process_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DealProcessTest < ActiveSupport::TestCase + fixtures :deal_processes + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/deal_status_test.rb b/test/unit/deal_status_test.rb new file mode 100644 index 0000000..58fae2b --- /dev/null +++ b/test/unit/deal_status_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DealStatusTest < ActiveSupport::TestCase + fixtures :deal_statuses + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/deal_test.rb b/test/unit/deal_test.rb new file mode 100644 index 0000000..0c6bb65 --- /dev/null +++ b/test/unit/deal_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class DealTest < ActiveSupport::TestCase + fixtures :deals + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/note_test.rb b/test/unit/note_test.rb new file mode 100644 index 0000000..1f72b05 --- /dev/null +++ b/test/unit/note_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class NoteTest < ActiveSupport::TestCase + fixtures :notes + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/recently_viewed_test.rb b/test/unit/recently_viewed_test.rb new file mode 100644 index 0000000..7610bb4 --- /dev/null +++ b/test/unit/recently_viewed_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class RecentlyViewedTest < ActiveSupport::TestCase + fixtures :recently_vieweds + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/tag_test.rb b/test/unit/tag_test.rb new file mode 100644 index 0000000..ae402a5 --- /dev/null +++ b/test/unit/tag_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TagTest < ActiveSupport::TestCase + fixtures :tags + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/tagging_test.rb b/test/unit/tagging_test.rb new file mode 100644 index 0000000..6c57c94 --- /dev/null +++ b/test/unit/tagging_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TaggingTest < ActiveSupport::TestCase + fixtures :taggings + + # Replace this with your real tests. + def test_truth + assert true + end +end