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
+
+
' + 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 << "
+ <%= 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 %>
+
+
+
+
+
+ <%= 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 %>
+
+ <% else %>
+
+ <% end %>
+
<%= f.text_field 'middle_name', :label=>l(:field_contact_middle_name) %>
+
<%= f.text_field :last_name, :label=>l(:field_contact_last_name), :id => 'contact_last_name' %>
+
<%= f.text_field 'company', :label=>l(:field_contact_company) -%>
+
<%= f.text_field :birthday, :size => 12 %><%= calendar_for('contact_birthday') %>
+
+
<%= f.text_field :job_title, :label => !@contact.is_company ? l(:field_contact_job_title) : l(:field_company_field) %>
+
<%= f.text_area 'address', :label=>l(:field_contact_address), :rows => 5 -%>
+
+
+
+ <%= f.text_field :phone, :label=>l(:field_contact_phone), :size => 80 -%>
+
+ <%= l(:text_comma_separated) %>
+
+
+
+
+ <%= f.text_field 'email', :label=>l(:field_contact_email), :size => 80 -%>
+
+ <%= l(:text_comma_separated) %>
+
+
+
<%= f.text_field 'website', :label=>l(:field_contact_website) -%>
+
<%= f.text_field 'skype_name', :label=>l(:field_contact_skype) -%>
+
+ <% @contact.custom_field_values.each do |value| %>
+
+ <%= custom_field_tag_with_label :contact, value %>
+
+ <% end -%>
+
+
<%= f.text_area :background , :cols => 80, :rows => 8, :class => 'wiki-edit', :label=>l(:field_contact_background) %>
+ <%= wikitoolbar_for 'contact_background' %>
+
+ <%= render :partial => "contacts_tags/tags_form" %>
+
+ <% if @project %>
+
<%= f.select :assigned_to_id, (@project.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true, :label => l(:label_assigned_to) %>
+ <% end %>
+
+
+
+
+
+
+
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 %>
+
+
+<% form_tag( {:controller => 'contacts',
+ :action => 'edit_tags',
+ :project_id => @project,
+ :id => @contact },
+ :multipart => true ) do %>
+
+ <%= render :partial => "contacts_tags/tags_form" %>
+
+ <%= submit_tag l(:button_save) %>
+ <%= link_to l(:button_cancel), {}, :onclick => "Element.hide('edit_tags_form'); Element.show('tags_data'); return false;" %>
+<% end %>
\ 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_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) %>
+
+
+
+
+ <% @contacts.each do |contact| %>
+
+ <%= avatar_to contact, :size => "16" %>
+ <%= link_to_source contact %>,
+ <%= h contact.job_title %>
+ <%= " #{l(:label_at_company)} " unless (contact.job_title.blank? or contact.company.blank?) %>
+ <% if contact.contact_company %>
+ <%= link_to contact.contact_company.name, {:controller => 'contacts', :action => 'show', :id => contact.contact_company.id } %>
+ <% else %>
+ <%= h contact.company %>
+ <% end %>
+ <%= "(#{l(:field_contact_tag_names)}: #{contact.tag_list})" if contact.tags.any? %>
+
+ <% end %>
+
+
+
+
+<% form_tag(:action => 'bulk_update') do %>
+<%= @contacts.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
+
+
+<%= l(:label_change_properties) %>
+
+
+ <%= l(:field_company) %>
+ <%= text_field_tag('contact[company]', '') %>
+
+
+
+ <%= l(:field_contact_job_title) %>/<%= l(:field_company_field) %>
+ <%= text_field_tag('contact[job_title]', '') %>
+
+
+<% @contacts.first.custom_field_values.each do |value| %>
+
+ <% value.value = '' %>
+ <%= custom_field_tag_with_label :contact, value %>
+
+<% end -%>
+
+
+
+ <%= l(:label_assigned_to) %>
+ <%= 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) %>
+
+
+
+ <%= l(:field_add_tags) %>
+ <%= 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')" %>
+
+
+
+ <%= l(:field_delete_tags) %>
+ <%= 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) %>
+
+ <%= l(:label_add_into) %>
+ <%= 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 %>
+
+
+
+ <%= l(:label_delete_from) %>
+ <%= 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 %>
+
+ <%= l(:label_filter_plural) %>
+
+
+ <%= label_tag l(:label_author) + " " %>
+ <%= select_tag :note_author_id, options_for_select(Note.available_authors(@project).collect{|u| [u.name, u.id]}.insert(0, [""]), params[:note_author_id]) %>
+
+
+
+
+
+
+ <%= 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 @@
+
+
+ <% if !@contact.nil? %>
+ <%= context_menu_link l(:button_edit), {:controller => 'contacts', :action => 'edit', :id => @contact, :project => @project}, :class => 'icon-edit', :disabled => !@can[:edit] %>
+ <% if User.current.logged? %>
+ <%= watcher_link(@contact, User.current) %>
+ <% end %>
+
+ <% if !@project.nil? %>
+ <%= context_menu_link l(:label_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project, :contact_id => @contact},
+ :class => 'icon-add-deal', :disabled => !@can[:create_deal] %>
+ <% if @contact.is_company? %>
+ <%= context_menu_link l(:label_add_employee), {:controller => 'contacts', :action => 'new', :project_id => @project, :contact => {:company => @contact.name}},
+ :class => 'icon-add-employee', :disabled => !@can[:edit] %>
+ <% end %>
+ <% end %>
+ <% else %>
+ <%= context_menu_link l(:button_edit), {:controller => 'contacts', :action => 'bulk_edit', :ids => @contacts.collect(&:id)},
+ :class => 'icon-edit', :disabled => !@can[:edit] %>
+ <% end %>
+
+ <%= context_menu_link l(:label_send_mail), {:controller => 'contacts', :action => 'edit_mails', :ids => @contacts.collect(&:id), :project_id => @project}, :class => 'icon-email', :disabled => !@can[:send_mails] %>
+
+
+ <%= context_menu_link l(:button_delete), {:controller => 'contacts', :action => 'bulk_destroy', :ids => @contacts.collect(&:id), :project_id => @project},
+ :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %>
+
+
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) %>
+
+
+
+
+ <% @contacts.each do |contact| %>
+
+ <%= avatar_to contact, :size => "16" %>
+ <%= link_to_source contact %>
+ <%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
+ - <%= contact.emails.first %>
+
+ <% end %>
+
+
+
+
+<% 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 %>
+
+
+
+ <%= l(:field_mail_from) %>
+ <%= text_field_tag('from', User.current.mail, :id => "email", :size => 30) %>
+
+
+
+
+
+ <%= l(:field_subject) %>
+ <%= text_field_tag('subject', '', :id => "subject", :size => 100) %>
+
+
+ <%= l(:field_message) %>
+ <%= 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' %>
+
+
+
+
+
+ <% 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? -%>
+
+
+
+ <%= 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) %>
+
+<% 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) %>
+
+ <% @contact.duplicates.each do |contact| %>
+
+
+ <%= avatar_to contact, :size => "16" %>
+ <%= link_to_source contact %>
+ <%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
+
+ <% end %>
+
+
+
+<% 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)) %>
+
+
+
+<% form_tag({:controller => 'contacts_duplicates', :action => 'merge', :project_id => @project, :contact_id => @contact}) do %>
+
+ <%= content_tag('div', l(:notice_merged_warning), :class => "flash warning") %>
+
+
+ <% @contact.duplicates.each do |contact| %>
+
+ <%= radio_button_tag "dublicate_id", contact.id %>
+ <%= avatar_to contact, :size => "16" %>
+ <%= link_to_source contact %>
+ <%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
+
+ <% end %>
+
+
+ <%= 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 @@
+
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| -%>
+
+
+ <%= l(:field_contact_tag_names) %>
+ <%= 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' %>
+
+
+
<%=l(:field_name)%>
+<%= text_field 'tag', 'name' %>
+
+
<%=l(:field_color)%>
+<%= 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' %>
+
+
+ <% form_tag({ :controller => "contacts_tasks", :action => "new", :project_id => @project, :contact_id => contact}, :multipart => true, :id => "add_task_form") do %>
+
+ <%= render :partial => 'contacts_tasks/attributes' %>
+
+ <%= link_to l(:button_cancel), {}, :onclick => "Element.hide('add_issue'); Element.show('add_task_link'); return false;" %>
+
+ <% end %>
+
+
+
+
+
+
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| %>
+ >
+
+ <%= 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) %>
+
+ <% end %>
+
+
+<% 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 %>
+
+ <%= file_field_tag 'contact_vcf', :size => 30, :id => nil, :onchange => "this.form.submit()" -%>
+
+ <%= link_to 'Cancel', {}, :onclick => "Element.toggle('import_link'); Element.toggle('import_file'); return false;" %>
+
+
+ <% 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) %>
+
<%= radio_button_tag 'todo', 'nullify', true %> <%= l(:text_deal_category_destroy_assignments) %>
+<% if @categories.size > 0 %>
+<%= radio_button_tag 'todo', 'reassign', false %> <%= l(:text_deal_category_reassign_to) %> :
+<%= 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? %>
+
+<% 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' %>
+
+
+
+
<%=l(:field_name)%> *
+<%= text_field 'deal_status', 'name' %>
+
+
+
<%=l(:field_color)%>
+<%= text_field 'deal_status', 'color_name', :class => "colorpicker" %>
+
+
+
+
+
+
<%=l(:field_deal_status_is_closed)%>
+<%= check_box 'deal_status', 'is_closed' %>
+
+
<%=l(:field_is_default)%>
+<%= 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)%>
+
+
+
+ <%=l(:field_status)%>
+ <%=l(:field_is_default)%>
+ <%=l(:field_is_closed)%>
+ <%=l(:button_sort)%>
+
+
+
+<% for status in @deal_statuses %>
+ ">
+ <%= 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') %>
+
+
+<% end %>
+
+
+
+
+
+<% 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? %>
+ <%= custom_value.custom_field.name %>: <%=h show_value(custom_value) %>
+ <% end %>
+ <% end %>
+
+
+
+<% 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| %>
+
+
+ >
+ <%= 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 %>
+
+
+<% 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? %>
+
+ <%= 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? %>
+
+<% 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) %>
+
+
+
+
+ <% @deals.each do |deal| %>
+
+ <%= avatar_to deal, :size => "16" %>
+ <%= link_to deal.full_name, polymorphic_url(deal) %>
+ <%= "(#{deal_price(deal)}) " unless deal.price.blank? %>
+ <% if deal.status %>
+ >
+ <%= h deal.status %>
+
+ <% end %>
+
+ <% end %>
+
+
+
+
+<% form_tag(:action => 'bulk_update') do %>
+<%= @deals.collect {|i| hidden_field_tag('ids[]', i.id)}.join %>
+
+
+<%= l(:label_change_properties) %>
+
+<% if @available_statuses.any? %>
+
+ <%= l(:field_status) %>
+ <%= 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? %>
+
+ <%= l(:field_category) %>
+ <%= 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 %>
+
+
+ <%= l(:label_assigned_to) %>
+ <%= 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)) %>
+
+
+ <%= l(:field_deal_currency) %>
+ <%= 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 @@
+
+ <% if !@deal.nil? %>
+ <%= context_menu_link l(:button_edit), {:controller => 'deals', :action => 'edit', :id => @deal},
+ :class => 'icon-edit', :disabled => !@can[:edit] %>
+
+
+ <% if User.current.logged? %>
+ <%= watcher_link(@deal, User.current) %>
+ <% end %>
+
+ <% else %>
+ <%= context_menu_link l(:button_edit), {:controller => 'deals', :action => 'bulk_edit', :ids => @deals.collect(&:id)},
+ :class => 'icon-edit', :disabled => !@can[:edit] %>
+ <% end %>
+
+ <% unless @project.nil? || @project.deal_categories.empty? -%>
+
+
+
+ <% @project.deal_categories.each do |u| -%>
+ <%= context_menu_link u.name, {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'category_id' => u}, :back_url => @back}, :method => :post,
+ :selected => (@deal && u == @deal.category), :disabled => !@can[:edit] %>
+ <% end -%>
+ <%= context_menu_link l(:label_none), {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
+ :selected => (@deal && @deal.category.nil?), :disabled => !@can[:edit] %>
+
+
+ <% end -%>
+
+ <% unless @project.nil? || @project.deal_statuses.empty? -%>
+
+
+
+ <% @project.deal_statuses.each do |s| -%>
+ <%= context_menu_link s.name, {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'status_id' => s}, :back_url => @back}, :method => :post,
+ :selected => (@deal && s == @deal.status), :disabled => !@can[:edit] %>
+ <% end -%>
+
+
+ <% end -%>
+
+
+ <%= context_menu_link l(:button_delete), {:controller => 'deals', :action => 'bulk_destroy', :ids => @deals.collect(&:id), :project_id => @project},
+ :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %>
+
+
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_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 authorize_for('notes', 'add_note') %>
+
+ <%= render :partial => 'notes/add', :locals => {:note_source => @deal} %>
+ <% end %>
+
+
+
+
+<% 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?) %>
+
+
+
+
+
+<% 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 %>
+
+
+
+ <% @issue.taskables.find_by_source_type(source_type.name).each do |taskable| %>
+
+ <%= avatar_to taskable, :size => "16" %>
+ <%= link_to_source taskable %>
+ <%= "(#{taskable.job_title}) " unless taskable.job_title.blank? %>
+
+ <% if User.current.allowed_to?("delete_#{source_type.name.underscore}s".to_sym, @project) %>
+ <%= link_to_remote(image_tag('delete.png'),
+ :url => {:controller => 'tasks',
+ :action => 'delete',
+ :issue_id => @issue,
+ :project_id => @project,
+ :source_id => taskable.id,
+ :source_type => source_type},
+ :method => :delete,
+ :confirm => l(:text_are_you_sure),
+ :html => {:class => "delete",
+ :title => l(:button_delete) }) %>
+
+ <% 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) %>
+
+
+
+ <% contacts.each do |contact| %>
+
+ <%= avatar_to contact, :size => "16" %>
+ <%= link_to_source contact %>
+ <%= "(#{contact.job_title}) " unless contact.job_title.blank? %>
+
+ <% end %>
+
+
+
+<% 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
+%>
+
+
+
+
+ <%= 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}) %>
+
+ <% 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| %>
+
+ >
+ >
+ <%= h status.name %>
+
+
+ <%= status.count %>
+
+ <% 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_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) %>
+
+
+
+ <% deals.each do |deal| %>
+
+ <%= avatar_to deal, :size => "16" %>
+ <%= link_to deal.full_name, polymorphic_url(deal) %>
+ <%= "(#{deal_price(deal)}) " unless deal.price.blank? %>
+ <% if deal.status %>
+ >
+ <%= h deal.status %>
+
+ <% end %>
+
+ <% end %>
+
+
+
+<% 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' %>
+
+
+
+
+
+ <% form_for(:note, @note, :url => add_note_url(note_source, @project), :html => {:multipart => true, :id => "add_note_form"}) do |f| %>
+ <%= render :partial => 'notes/form', :locals => {:f => f, :ajax_form => false} %>
+ <%= submit_tag l(:button_add_note) %>
+ <% end %>
+
+ <%= link_to l(:label_note_hide_extras), {}, :onclick => "Element.toggle('add_html_node'); Element.toggle('add_ajax_node'); $('add_ajax_node').down('.wiki-edit').value = $('add_html_node').down('.wiki-edit').value; return false;", :id => 'hide_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 %>
+
+<% 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" %>
+
+
+
+ <%= 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 @@
+
\ 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 %>
+ <%= link_to avatar_to(note_item.source, :size => "32"), note_source_url(note_item.source), :id => "avatar" %>
+ <% end %>
+
+
+ <%= 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 %>
+
+<% 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| %>
+
+
+
+
+ <%= l(:field_type) %> <%= f.select :type_id, collection_for_note_types_select, { :include_blank => true } %>
+
+
+ <%= l(:field_note_date) %> <%= 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 %>
+
<%= l(:label_attachment_plural) %>
+
+ <%= 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 %>
+
+
+
+ <%= l(:label_show_deaks_tab) %>
+ <%= 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 %>
+
+
+
+ <%= l(:label_show_on_projects_show) %>
+ <%= 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 %>
+
+
+ <%= l(:field_name) %>
+ <%=l(:field_is_default)%>
+ <%=l(:field_deal_status_is_closed)%>
+ <%= l(:field_active) %>
+
+
+ <% DealStatus.all.each do |status| %>
+
+
+ <%= 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) %>
+
+
+ <% end %>
+
+
+ <%= 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? %>
+
+
+ <%= l(:field_name) %>
+
+
+
+<% for category in @project.deal_categories %>
+ <% unless category.new_record? %>
+
+ <%= 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' %>
+
+
+ <% end %>
+<% end %>
+
+
+<% 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? %>
+
+
+ <%= h l(:label_deal_status) %>
+ <%= h l(:label_count) %>
+ <%= h l(:label_total) %>
+
+ <% @sale_funel.each do |deal_status, deals_count, deals_sum| %>
+
+ >
+
+ <%= h "#{deal_status.name}" %>
+
+
+
+
+ <%= h deals_count %>
+
+
+
+
+ <%= deals_sum_to_currency deals_sum %>
+
+
+
+
+ <% end %>
+
+<% 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)%>
+
+
+
+ <%=l(:field_status)%>
+ <%=l(:field_is_default)%>
+ <%=l(:field_deal_status_is_closed)%>
+ <%=l(:button_sort)%>
+
+
+
+<% for status in DealStatus.all( :order => "position") %>
+ ">
+ <%= 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') %>
+
+
+<% end %>
+
+
+
+
+<% 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 @@
+
+ <%= l(:label_gravatar_enabled) %>
+ <%= check_box_tag 'settings[use_gravatar]', 1, @settings[:use_gravatar] %>
+
+
+
+ <%= l(:label_user_format) %>
+ <%= 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] ) %>
+
+
+
+ <%= l(:label_thumbnails_enabled) %>
+ <%= check_box_tag 'settings[auto_thumbnails]', 1, @settings[:auto_thumbnails] %>
+
+
+<% if !Object.const_defined?(:Magick) %>
+
+ <%= l(:label_max_thumbnail_file_size) %>
+ <%= 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) %>
+
+
+
+ <%= l(:field_name) %>
+
+
+
+ <% ActsAsTaggableOn::Tag.find(:all, :include => :taggings, :conditions => "#{ActsAsTaggableOn::Tagging.table_name}.taggable_type = 'Contact'", :order => :name).each do |tag| %>
+ hascontextmenu ">
+ <%= 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' %>
+
+
+
+
+ <% end %>
+
+
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? %>
+
+
+
+<% 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:
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
<%= l(:label_note_plural) %>
+<%= pagination_links @notes_pages, :params => {:project_id => params[:project_id]}%>
+