Initial Redmine tooling and local plugin forks

This commit is contained in:
Jason Thistlethwaite
2026-04-24 22:01:18 +00:00
commit 9f682af0eb
683 changed files with 56878 additions and 0 deletions
@@ -0,0 +1,14 @@
<% if @deal.custom_field_values.any? %>
<div id="attributes">
<h3><%= l(:label_deal) %></h3>
<table class="attributes">
<% @deal.custom_field_values.each do |custom_value| %>
<% if !custom_value.value.blank? %>
<tr> <th class = "custom_field"><%= custom_value.custom_field.name %>:</th><td> <%=h show_value(custom_value) %></td> </tr>
<% end %>
<% end %>
</table>
</div>
<% end %>
@@ -0,0 +1,7 @@
<tr>
<% deal_statuses.each do |deal_status| %>
<th style="width: <%= 100/deal_statuses.size %>%;">
<%= "#{deal_status.name} (#{@deals_scope.where(:status_id => deal_status.id).count})"%>
</th>
<% end %>
</tr>
@@ -0,0 +1,7 @@
<tr>
<% deal_statuses.each do |deal_status| %>
<th style="width: <%= 100/deal_statuses.size %>%;" data-id="<%= deal_status.id %>">
<%= prices_collection_by_currency(@deals_scope.group(:currency).where(:status_id => deal_status.id).sum(:price), :hide_zeros => true).join('<br/>').html_safe %>
</th>
<% end %>
</tr>
@@ -0,0 +1,5 @@
<p><%= form.check_box :is_filter %></p>
<p><%= form.check_box :visible, :label => l(:label_crm_contacts_show_in_list) %></p>
<% if (@custom_field.respond_to?(:format) && @custom_field.format.searchable_supported) || !@custom_field.respond_to?(:format) %>
<p><%= form.check_box :searchable %></p>
<% end %>
@@ -0,0 +1,24 @@
<% if deal_statuses.any? %>
<div id="deals_statistics">
<div class="contextual">
<%= link_to l(:label_crm_sales_funnel), {:controller => 'deals', :action => 'index', :project_id => @project, :deals_list_style => 'list_pipeline'} %>
</div>
<h3><%= l(:label_crm_statistics) %></h3>
<table class="deals_statistics">
<% deal_statuses.each do |deal_status| %>
<tr>
<td>
<span class="tag-label-color" style=<%= "background-color:#{deal_status.color_name};color:white;" %> >
<%= link_to "#{deal_status.name}(#{@project ? @project.deals.visible.with_status(deal_status.id).count : Deal.visible.with_status(deal_status.id).count})".html_safe, deal_status_url(deal_status.id, :project_id => @project) %>
</span>
</td>
<td>
<strong>
<%= prices_collection_by_currency(Deal.by_project(@project).visible.where(:status_id => deal_status.id).group(:currency).sum(:price), :hide_zeros => true).join(' / ').html_safe %>
</strong>
</td>
</tr>
<% end %>
</table>
</div>
<% end %>
@@ -0,0 +1,68 @@
<%= labelled_fields_for :deal, @deal do |f| %>
<%= call_hook(:view_deals_form_details_top, {:deal => @deal, :form => f }) %>
<p><%= f.text_field :name, :label=>l(:field_deal_name), :size => 80, :required => true %></p>
<p><%= f.select :project_id, project_tree_options_for_select(Deal.allowed_target_projects, :selected => @deal.project), {:required => true}, :onchange => "updateCustomForm('#{escape_javascript update_form_deals_path(:id => @deal, :format => 'js')}', '#deal_form')" %></p>
<p class = "notes"><%= f.text_area :background , :cols => 80, :rows => 8, :class => 'wiki-edit', :label=>l(:field_deal_background) %></p> <%= wikitoolbar_for 'deal_background' %>
<div id="attributes" class="attributes">
<div class="splitcontentleft">
<% if @project.deal_statuses.any? %>
<p><%= f.select :status_id, collection_for_status_select, :include_blank => false, :label=>l(:field_contact_status) %></p>
<% end %>
<p>
<%= label_tag :deal_contact_id, RedmineContacts.companies_select ? l(:label_crm_company) : l(:label_contact)%>
<%= select_contact_tag('deal[contact_id]',
@deal.contact,
:include_blank => true,
:add_contact => true,
:is_company => RedmineContacts.companies_select) %>
</p>
<p>
<% if RedmineContacts.products_plugin_installed? %>
<%= f.text_field :price, :label => l(:field_deal_price), :size => 10, :disabled => @deal.lines.present? %>
<% else %>
<%= f.text_field :price, :label => l(:field_deal_price), :size => 10 %>
<% end %>
<%= select_tag "deal[currency]", options_for_select(collection_for_currencies_select(ContactsSetting.default_currency, ContactsSetting.major_currencies), @deal.currency), :include_blank => true, :style => "width: initial;"
%>
</p>
<p><%= f.select :assigned_to_id, (@project.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true, :label => l(:label_crm_assigned_to) %></p>
</div>
<div class="splitcontentright">
<% unless @project.deal_categories.empty? %>
<p><%= f.select :category_id, (@project.deal_categories.collect {|c| [c.name, c.id]}), :include_blank => true %></p>
<% end %>
<p><%= f.select :probability, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }), :label => l(:label_crm_probability), :include_blank => true %></p>
<p><%= f.text_field :due_date, :label => l(:field_due_date), :size => 12 %><%= calendar_for('deal_due_date') %></p>
</div>
</div>
<div style="clear:both;"> </div>
<% custom_field_values = @deal.custom_field_values %>
<div class="splitcontent">
<div class="splitcontentleft">
<% i = 0 %>
<% split_on = (custom_field_values.size / 2.0).ceil - 1 %>
<% custom_field_values.each do |value| %>
<p><%= custom_field_tag_with_label :deal, value, :required => value.custom_field.is_required? %></p>
<% if i == split_on -%>
</div><div class="splitcontentright">
<% end -%>
<% i += 1 -%>
<% end -%>
</div>
</div>
<% if RedmineContacts.products_plugin_installed? %>
<p class="<%= 'hol' if @deal.lines.present? %>">
<%= label_tag l(:label_deal_items) %>
<span><%= link_to('Add new', 'javascript:void(0)', :onclick => 'toogleDealItems(this); return false;', :class => 'icon icon-add') %></span>
<p>
<%= field_set_tag(l(:label_deal_items), :class => "deal_items #{'hol' unless @deal.lines.present?}") do %>
<%= render :partial =>'shared/new_product_line', :locals => { :form => f, :parent_object => @deal} %>
<% end %>
<% end %>
<%= call_hook(:view_deals_form_details_bottom, {:deal => @deal, :form => f }) %>
<% end %>
@@ -0,0 +1,45 @@
<% product = f.object.product %>
<tr class="line fields sortable-line" id="line-<%= f.object.id %>" >
<% unless product.blank? %>
<td class="product-image"><%= product_tag(product, :size => 32, :type => 'image') %></td>
<% end %>
<td class="item" colspan="<%= product.blank? ? 2 : 1 %>">
<%= product_tag(product, :type => 'plain') unless product.blank? %>
<%= f.hidden_field :product_id %>
<% if !product.blank? && f.object.description.blank? %>
<br>
<em class="info"><%= link_to_function "(#{l(:label_products_add_description)})", "$(this).hide(); $(this).parent().next().show(); return false;" %></em>
<% end %>
<%= f.text_area :description, :no_label => true, :rows => f.object.description.blank? ? 2 : [f.object.description.lines.count, 2].max, :onkeyup => "activateTextAreaResize(this);", :style => "width:99%; #{(product.blank? || !f.object.description.blank?) ? "" : "display:none;"}" -%>
</td>
<% f.object.custom_field_values.each do |cf| %>
<td><%= custom_field_tag("order[lines_attributes][#{f.index}]", cf) %></td>
<% end %>
<td class="quantity"><%= f.text_field :quantity, :no_label => true, :size => 6, :onkeyup => 'updateTotal(this)' %></td>
<td class="price"><%= f.text_field :price, :no_label => true, :size => 8, :onkeyup => 'updateTotal(this)' %></td>
<% if !ContactsSetting.disable_taxes? || (f.object.container.respond_to?(:has_taxes?) && f.object.container.has_taxes?) %>
<td class="tax">
<% line_tax = (f.object.new_record? && f.object.tax.blank?) ? ContactsSetting.default_tax : f.object.tax %>
<%= check_box_tag :show_tax, "1", false, :onclick=>"$(this).hide(); $(this).parent().find('.tax-fields').show(); $(this).next().find('input').focus(); return false;" if line_tax.blank? || line_tax == 0 %>
<span class="tax-fields" style="white-space: nowrap; <%= line_tax.blank? || line_tax == 0 ? "display:none;" : "" %>"><%= f.text_field :tax, :no_label => true, :size => 5, :value => line_tax %>&nbsp;%
</span>
</td>
<% end %>
<td class="discount">
<%= check_box_tag :show_discount, "1", false, :onclick=>"$(this).hide(); $(this).parent().find('.discount-fields').show(); $(this).next().find('input').focus(); return false;" if f.object.discount.to_i == 0 %>
<span class="discount-fields" style="white-space: nowrap; <%= f.object.discount.to_i == 0 ? "display:none;" : "" %>">
<%= f.text_field :discount, :no_label => true, :size => 5, :style => f.object.discount.to_i == 0 ? "display:none;" : "", :onkeyup => 'updateTotal(this)', :class => "discount-fields" %>&nbsp;%
</span>
</td>
<td class="total"><%= format("%.2f\n", f.object.total) if f.object.price && f.object.quantity %></td>
<td>
<%= deals_link_to_remove_fields "", f, :class => "icon icon-del" %>
</td>
<%= f.hidden_field :position, :class => 'position' %>
</tr>
<script type="text/javascript">
tooglePriceField();
</script>
@@ -0,0 +1,71 @@
<%= form_tag({}, :data => {:cm_url => context_menu_deals_path}) do -%>
<%= hidden_field_tag 'back_url', url_for(params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params), :id => nil %>
<div class="autoscroll">
<table class="list deals issues contacts">
<thead>
<tr>
<th class="checkbox hide-when-print">
<%= link_to image_tag('toggle_check.png'), {},
:onclick => 'toggleCRMIssuesSelection(this); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
<% @query.inline_columns.each do |column| %>
<%= Redmine::VERSION.to_s >= '3.4' || RedmineContacts.unstable_branch? ? column_header(@query, column) : column_header(column) %>
<% end %>
</tr>
</thead>
<% previous_group = false %>
<tbody>
<% @deals.each do |deal| -%>
<% if @query.grouped? && (group = @query.group_by_column.value(deal)) != previous_group %>
<% reset_cycle %>
<tr class="group open">
<td colspan="<%= @query.inline_columns.size + 2 %>">
<span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
<%= group.blank? ? l(:label_none) : column_content(@query.group_by_column, deal) %> <span class="count"><%= @deal_count_by_group[group] %></span>
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}",
"toggleAllRowGroups(this)", :class => 'toggle-all') %>
</td>
</tr>
<% previous_group = group %>
<% end %>
<tr id="deal-<%= deal.id %>" class="hascontextmenu <%= cycle('odd', 'even') %>">
<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", deal.id, false, :id => nil) %></td>
<td class="id"><%= link_to deal.id, deal_path(deal) %></td>
<%= raw @query.inline_columns.map {|column| "<td class=\"#{column.css_classes}\">#{column_content(column, deal)}</td>"}.join %>
</tr>
<% @query.block_columns.each do |column|
if (text = column_content(column, deal)) && text.present? -%>
<tr class="<%= current_cycle %>">
<td colspan="<%= @query.inline_columns.size + 1 %>" class="<%= column.css_classes %>"><%= text %></td>
</tr>
<% end -%>
<% end -%>
<% end -%>
<tr class="total">
<th colspan="<%= @query.inline_columns.size + 2 %>">
<table class="contacts deals index total">
<tbody>
<tr class="total">
<% if @deal_weighted_amount.map{|k, v| v.to_i > 0}.any? %>
<th class="title"><%= l(:label_crm_expected_revenue) %>:</th>
<th class="sum deals-sum">
<%= prices_collection_by_currency(@deal_weighted_amount, :hide_zeros => true).join('<br/>').html_safe %>
</th>
<% end %>
<th class="title"><%= "#{l(:label_total)} (#{@deals_count}):" %></th>
<th class="sum deals-sum">
<%= prices_collection_by_currency(@deal_amount, :hide_zeros => true).join('<br/>').html_safe %>
</th>
</tr>
</tbody>
</table>
</th>
</tr>
</tbody>
</table>
</div>
<% end -%>
@@ -0,0 +1,104 @@
<% if deal_statuses.any? %>
<% if User.current.allowed_to?(:edit_deals, @project) %>
<script>
$(function() {
$(".deal-status-col").droppable({
activeClass: 'droppable-active',
hoverClass: 'droppable-hover',
accept: '.deal-card',
tolerance: 'pointer',
drop: function (event, ui) {
ui.draggable.prependTo(this);
$.ajax({
url: '<%= escape_javascript deals_path %>/' + ui.draggable.data('id'),
type: 'PUT',
dataType : "script",
data: {
deal: {status_id: $(this).data("id")},
status_id: $("#operators_status_id").val()
},
success: function(data){
}
});
}
});
$(".deal-card").draggable({
containmentType: "parent",
helper: "clone",
start: function (event, ui) {
$(ui.helper).addClass("draggable-active")
}
});
// $(".deal-status-col" ).sortable({
// connectWith: ".deal-status-col",
// placeholder: "sortable-placeholder",
// receive: function (event, ui) {
// var deal_id = ui.item.data('id');
// var status_id = $(this).data("id");
// $.ajax({
// url: '<%= escape_javascript deals_path %>/' + deal_id + '.json',
// dataType : "json",
// type: 'PUT',
// data: {deal: {status_id: status_id}}
// });
// }
// }).disableSelection();
});
</script>
<% end %>
<%= form_tag({}, :data => {:cm_url => context_menu_deals_path}) do -%>
<%= hidden_field_tag 'back_url', url_for(params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params) %>
<%= hidden_field_tag 'project_id', @project.id if @project %>
<% board_statuses = params[:status_id] == 'o' ? deal_statuses.open : deal_statuses %>
<div class="autoscroll">
<table class="list deal-board ">
<thead class="deals_counts">
<%= render :partial => 'board_deals_counts' %>
</thead>
<tr style="text-align: center;white-space: nowrap;" class="deal <%= cycle('odd', 'even') %>">
<% board_statuses.each do |deal_status| %>
<td class="deal-status-col <%='open' if deal_status.is_open? %> <%='won' if deal_status.is_won? %> <%='lost' if deal_status.is_lost? %> <%='closed' if deal_status.is_closed? %>" data-id="<%= deal_status.id %>">
<% @deals.where(:status_id => deal_status.id).order("#{Deal.table_name}.updated_on DESC").each do |deal| %>
<div class="deal-card" data-id="<%= deal.id %>">
<p class="amount">
<strong><%= deal.price_to_s %></strong>
<%= content_tag(:span, " (#{deal.probability}%)" ) if deal.probability %>
</p>
<p class="name" ><%= link_to deal.name, deal_path(deal) %></p>
<% if deal.contact %>
<p class="info">
<span class="contact"><%= contact_tag(deal.contact) %></span>
</p>
<% end %>
</div>
<% end %>
</td>
<% end %>
</tr>
<thead class="total">
<%= render :partial => 'board_total' %>
<!-- <tr>
<% board_statuses.each do |deal_status| %>
<th style="width: <%= 100/board_statuses.size %>%;" data-id="<%= deal_status.id %>">
<%= prices_collection_by_currency(@deals_scope.group(:currency).where(:status_id => deal_status.id).sum(:price), :hide_zeros => true).join('<br/>').html_safe %>
</th>
<% end %>
</tr> -->
</thead>
</table>
</div>
<% end %>
<% else %>
<p class="nodata"><%= l(:text_crm_no_deal_statuses_in_project) %></p>
<% end %>
@@ -0,0 +1,71 @@
<%= form_tag({}, :data => {:cm_url => context_menu_deals_path}) do -%>
<%= hidden_field_tag 'back_url', url_for(params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params) %>
<%= hidden_field_tag 'project_id', @project.id if @project %>
<div class="autoscroll">
<table class="contacts deals index">
<tbody>
<% previous_group = false %>
<% @deals.each do |deal| %>
<% if @query.grouped? && (group = @query.group_by_column.value(deal)) != previous_group %>
<% reset_cycle %>
<tr class="group open">
<td colspan="<%= @query.columns.size + 2 %>">
<span class="expander" onclick="toggleRowGroup(this);">&nbsp;</span>
<%= group.blank? ? 'None' : column_content(@query.group_by_column, deal) %> <span class="count">(<%= @deal_count_by_group[group] %>)</span>
<%= link_to_function("#{l(:button_collapse_all)}/#{l(:button_expand_all)}", "toggleAllRowGroups(this)", :class => 'toggle-all') %>
</td>
</tr>
<% previous_group = group %>
<% end %>
<tr class="hascontextmenu <%= deal.status_id %> <%= cycle('odd', 'even') %> ">
<td class="checkbox">
<%= check_box_tag "ids[]", deal.id, false, :onclick => "toggleContact(event, this);" %>
</td>
<td class="avatar">
<%= link_to avatar_to(deal, :size => "32"), {:controller => 'deals', :action => 'show', :id => deal.id}, :id => "avatar" %>
</td>
<td class="name">
<h1 class="deal_name"><%= link_to deal.name, :controller => 'deals', :action => 'show', :id => deal.id %></h1>
<p>
<%= link_to_source(deal.contact) if deal.contact %>
</p>
</td>
<td class="info">
<div class="deal-sum"><strong><%= deal.price_to_s %></strong>
<%= content_tag(:span, " (#{deal.probability}%)" ) if deal.probability %>
<% if deal.status && deal.project.deal_statuses.any? %>
<%= deal_status_tag(deal.status) %>
<% end %>
</div>
<div class="description" >
<%= h deal.category %><%= " (#{format_date(deal.due_date)})" if deal.due_date %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
<table class="contacts deals index total">
<tbody>
<tr class="total">
<% if @deal_weighted_amount.map{|k, v| v.to_i > 0}.any? %>
<th class="title"><%= l(:label_crm_expected_revenue) %>:</th>
<th class="sum deals-sum">
<%= prices_collection_by_currency(@deal_weighted_amount, :hide_zeros => true).join('<br/>').html_safe %>
</th>
<% end %>
<th class="title"><%= "#{l(:label_total)} (#{@deals_count}):" %></th>
<th class="sum deals-sum">
<%= prices_collection_by_currency(@deal_amount, :hide_zeros => true).join('<br/>').html_safe %>
</th>
</tr>
</tbody>
</table>
</div>
<% end %>
@@ -0,0 +1,45 @@
<% if deal_statuses.any? %>
<% @deals = @query.results_scope(
:include => [{:contact => [:avatar, :projects, :address]}, :author]
)
@processor = DealsPipelineProcessor.new(@deals) %>
<div class="autoscroll">
<table class="list sales-funnel">
<thead>
<tr>
<th><%= h l(:label_crm_deal_status) %></th>
<th><%= h l(:label_crm_count) %></th>
<th><%= h l(:label_total) %></th>
</tr>
</thead>
<% deal_statuses.each_with_index do |deal_status, index| %>
<% status_scope = @processor.deals_for_status(deal_status) %>
<tr class="deal_status_type-<%= deal_status.status_type %>">
<td class="sales-funnel index_<%= deal_status.is_lost? ? 1 : index.to_s %>" >
<%= pipeline_status_tag(deal_status, status_scope.count, index) %>
</td>
<td class="count">
<strong>
<%= h status_scope.size %>
</strong>
</td>
<td class="total">
<strong>
<%= pipeline_prices(status_scope) %>
</strong>
</td>
</tr>
<% end %>
<tr class="total">
<th colspan="2" class="title" style="text-align: right;"> <%= "#{l(:label_total)} (#{@processor.count}):" %> </th>
<th class="sum" style="text-align: right;">
<%= pipeline_prices(@processor.scope) %>
</th>
</tr>
</table>
</div>
<% else %>
<p class="nodata"><%= l(:text_crm_no_deal_statuses_in_project) %></p>
<% end %>
@@ -0,0 +1,29 @@
<% show_info = true if show_info.nil? %>
<% show_author = true if !show_author.nil? %>
<div id=<%="note_#{process_item.id}"%>>
<table class="note_data">
<tr>
<% if show_info %>
<% if show_author %>
<td class="avatar"><%= link_to avatar(process_item.author, :size => "32"), note_source_url(process_item.deal), :id => "avatar" %></td>
<% else %>
<td class="avatar"><%= link_to avatar_to(process_item.deal, :size => "32"), note_source_url(process_item.deal), :id => "avatar" %></td>
<% end %>
<% end %>
<td class="name">
<h4>
<%# note_type_icon(process_item) %>
<%= link_to_source(process_item.deal) + "," if show_info %>
<%= authoring_note process_item.created_at, process_item.author %>
</h4>
<div class="wiki note">
<%= deal_status_tag(process_item.from) + " &rarr; ".html_safe if process_item.from %><%= deal_status_tag(process_item.to) if process_item.to %>
</div>
</td>
</tr>
</table>
</div>
@@ -0,0 +1,28 @@
<% @deals = @contact.all_visible_deals %>
<div id="deals">
<div class="contextual">
<%= link_to_if_authorized l(:label_crm_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project, :contact_id => @contact} %>
</div>
<h3><%= "#{l(:label_deal_plural)}" %> <%= " - #{prices_collection_by_currency(@deals.select{|d| d.open? && !d.price.blank?}.group_by(&:currency).map{|k, v| [k, v.sum(&:price)]}).join(' / ')}".html_safe if @deals.detect{|d| d.open? && !d.price.blank?} %></h3>
<% if @deals.any? %>
<table class="related_deals">
<% @deals.each do |deal| %>
<tr class="deal">
<td class="name" style="vertical-align: top;">
<p>
<%= deal_status_tag(deal.status) if deal.status %>
<%= link_to "#{deal.name}", deal_path(deal) %> -
<%= deal.price_to_s %>
</p>
</td>
</tr>
<% end %>
</table>
<% end %>
</div>
@@ -0,0 +1,99 @@
<h2><%= l(:label_crm_bulk_edit_selected_deals) %></h2>
<div class="box" id="duplicates">
<ul>
<% @deals.each do |deal| %>
<li>
<%= avatar_to deal, :size => "16" %>
<%= link_to deal.full_name, polymorphic_url(deal) %>
<%= "(#{deal.price_to_s}) " unless deal.price.blank? %>
<%= deal_status_tag(deal.status) if deal.status %>
</li>
<% end %>
</ul>
</div>
<%= form_tag(:action => 'bulk_update') do %>
<%= @deals.collect {|i| hidden_field_tag('ids[]', i.id)}.join.html_safe %>
<div class="box tabular">
<fieldset class="attributes">
<legend><%= l(:label_change_properties) %></legend>
<div class="splitcontentleft">
<p>
<label><%= l(:field_project) %></label>
<%= select_tag 'deal[project_id]', content_tag('option', l(:label_no_change_option), :value => '') + project_tree_options_for_select(Deal.allowed_target_projects) %>
</p>
<% if @available_statuses.any? %>
<p>
<label><%= l(:field_status) %></label>
<%= select_tag('deal[status_id]', content_tag('option', l(:label_no_change_option), :value => '') +
options_from_collection_for_select(@available_statuses, :id, :name)) %>
</p>
<% end %>
<p>
<label><%= l(:label_crm_assigned_to) %></label>
<%= select_tag('deal[assigned_to_id]', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_nobody), :value => 'none') +
options_from_collection_for_select(@assignables, :id, :name)) %>
</p>
<% @custom_fields.each do |custom_field| %>
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('deal', custom_field, @projects) %></p>
<% end %>
<% @deals.first.custom_field_values.each do |value| %>
<p>
<% value.value = '' %>
<%= custom_field_tag_with_label :contact, value %>
</p>
<% end -%>
</div>
<div class="splitcontentright">
<% if @available_categories.any? %>
<p>
<label><%= l(:field_category) %></label>
<%= select_tag('deal[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
content_tag('option', l(:label_none), :value => 'none') +
options_from_collection_for_select(@available_categories, :id, :name)) %>
</p>
<% end %>
<p>
<label><%= l(:field_deal_currency) %></label>
<%= select_tag "deal[currency]", content_tag('option', l(:label_no_change_option), :value => '') + options_for_select(collection_for_currencies_select(ContactsSetting.default_currency, ContactsSetting.major_currencies)) %>
</p>
<p>
<label for="deal_due_date"><%= l(:field_due_date) %></label>
<%= text_field_tag "deal[due_date]", "", :size => 12 %><%= calendar_for('deal_due_date') %>
</p>
<p>
<label for="deal_probability"><%= l(:label_crm_probability) %></label>
<%= select_tag "deal[probability]", content_tag('option', l(:label_no_change_option), :value => '') + options_for_select((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %>
</p>
</div>
</fieldset>
<fieldset><legend><%= l(:field_notes) %></legend>
<%= text_area_tag 'note[content]', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %>
<%= wikitoolbar_for 'note_content' %>
</fieldset>
</div>
<p><%= submit_tag l(:button_submit) %></p>
<% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<% end %>
@@ -0,0 +1,54 @@
<ul>
<% if !@deal.nil? %>
<li><%= context_menu_link l(:button_edit), {:controller => 'deals', :action => 'edit', :id => @deal},
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% if User.current.logged? %>
<li><%= watcher_link(@deal, User.current) %></li>
<% end %>
<% else %>
<li><%= context_menu_link l(:button_edit), {:controller => 'deals', :action => 'bulk_edit', :ids => @deals.collect(&:id)},
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% end %>
<% unless @project.nil? || @project.deal_categories.empty? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_category) %></a>
<ul>
<% @project.deal_categories.each do |u| -%>
<li><%= context_menu_link u.name, {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'category_id' => u}, :back_url => @back}, :method => :post,
:selected => (@deal && u == @deal.category), :disabled => !@can[:edit] %></li>
<% end -%>
<li><%= context_menu_link l(:label_none), {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'category_id' => 'none'}, :back_url => @back}, :method => :post,
:selected => (@deal && @deal.category.nil?), :disabled => !@can[:edit] %></li>
</ul>
</li>
<% end -%>
<% unless @project.nil? || @project.deal_statuses.empty? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_contact_status) %></a>
<ul>
<% @project.deal_statuses.each do |s| -%>
<li><%= context_menu_link s.name, {:controller => 'deals', :action => 'bulk_update', :ids => @deals.collect(&:id), :deal => {'status_id' => s}, :back_url => @back}, :method => :post,
:selected => (@deal && s == @deal.status), :disabled => !@can[:edit] %></li>
<% end -%>
</ul>
</li>
<% end -%>
<li><%= context_menu_link l(:button_delete), {:controller => 'deals', :action => 'bulk_destroy', :ids => @deals.collect(&:id), :project_id => @project, :back_url => @back},
:method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon-del', :disabled => !@can[:delete] %>
</li>
<% unless @deal && Redmine::VERSION.to_s >= '3.3'%>
<li>
<%= context_menu_link l(:button_filter), _project_deals_path(@project, :set_filter => 1, :ids => @deals.map(&:id).join(','), :object_type => "deal"),
:class => 'icon-list' %>
</li>
<% end %>
</ul>
@@ -0,0 +1,16 @@
<h2><%= l(:label_crm_deal_edit_information) %></h2>
<%= labelled_form_for :deal, @deal, :url => {:action => 'update', :id => @deal}, :html => {:method => :put, :id => "deal_form"} do |f| %>
<%= error_messages_for 'deal' %>
<div class="box tabular">
<div id="all_attributes">
<%= render :partial => 'form', :locals => {:f => f} %>
</div>
</div>
<%= submit_tag l(:button_save) -%>
<% end -%>
<% content_for :header_tags do %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<%= robot_exclusion_tag %>
<% end %>
@@ -0,0 +1,32 @@
api.array :deals, api_meta(:total_count => @deals_count, :offset => @offset, :limit => @limit) do
@deals.each do |deal|
api.deal do
api.id deal.id
api.name deal.name
api.price deal.price
api.currency deal.currency
api.price_type deal.price_type
api.duration deal.duration
api.probability deal.probability
api.due_date deal.due_date
api.background deal.background
api.project(:id => deal.project_id, :name => deal.project.name) unless deal.project.nil?
api.status(:id => deal.status_id, :name => deal.status.name) unless deal.status.nil?
api.category(:id => deal.category_id, :name => deal.category.name) unless deal.category.nil?
api.author(:id => deal.author_id, :name => deal.author.name) unless deal.author.nil?
api.contact(:id => deal.contact_id, :name => deal.contact.name) unless deal.contact.nil?
api.assigned_to(:id => deal.assigned_to_id, :name => deal.assigned_to.name) unless deal.assigned_to.nil?
api.array :related_contacts do
deal.related_contacts.each do |contact|
api.contact(:id => contact.id, :name => contact.name)
end
end if deal.related_contacts.any?
render_api_custom_values deal.custom_field_values, api
api.created_on deal.created_on
api.updated_on deal.updated_on
end
end
end
@@ -0,0 +1,151 @@
<% filtered_params = params.respond_to?(:to_unsafe_hash) ? params.to_unsafe_hash : params %>
<div class="contextual">
<% if !@query.new_record? && @query.editable_by?(User.current) %>
<%= link_to l(:button_contacts_edit_query), edit_crm_query_path(@query, :object_type => "deal"), :class => 'icon icon-edit' %>
<%= link_to l(:button_contacts_delete_query), crm_query_path(@query, :object_type => "deal"), :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :class => 'icon icon-del' %>
<% end %>
<%= link_to l(:label_crm_deal_new), {:controller => 'deals', :action => 'new', :project_id => @project || Deal.allowed_target_projects.first }, :class => 'icon icon-add' if User.current.allowed_to?(:add_deals, @project, {:global => true}) && Deal.allowed_target_projects.any? %>
<%= link_to_if_authorized l(:label_crm_import), {:controller => 'deal_imports', :action => 'new', :project_id => @project}, :class => 'icon icon-import', :id => 'import_from_csv' %>
</div>
<% html_title(@query.new_record? ? l(:label_deal_plural) : @query.name) %>
<%= form_tag({ :controller => 'deals', :action => 'index', :project_id => @project }, :method => :get, :id => 'query_form') do %>
<script type="text/javascript">
jQuery(function($) {
// when the #search field changes
$("#search").observe_field(2, function() {
var form = $("#query_form"); // grab the form wrapping the search bar.
var url = form.attr("action");
form.find('[name="c[]"] option').each(function(i, elem){
$(elem).attr('selected', true);
});
var formData = form.serialize();
form.find('[name="c[]"] option').each(function(i, elem){
$(elem).attr('selected', false);
});
$.get(url, formData, function(data) { // perform an AJAX get, the trailing function is what happens on successful get.
$("#contact_list").html(data); // replace the "results" div with the result of action taken
});
});
});
</script>
<h2 class="contacts_header">
<span id='scope_header' class="scope_title">
<%= @query.new_record? ? l(:label_deal_plural) : h(@query.name) %>
</span>
<span class="live_search">
<%= text_field_tag(:search, params[:search], :autocomplete => "off", :class => "live_search_field", :placeholder => l(:label_crm_contact_search) ) %>
</span>
</h2>
<%= hidden_field_tag 'set_filter', '1' %>
<%= hidden_field_tag 'object_type', 'deal' %>
<div id="query_form_content" class="hide-when-print">
<fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
<div style="<%= @query.new_record? ? "" : "display: none;" %>">
<%= render :partial => 'queries/filters', :locals => {:query => @query} %>
</div>
</fieldset>
<fieldset class="collapsible collapsed">
<legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
<div style="display: none;">
<table>
<tr style="<%= 'display: none;' if deals_list_style != 'list' %>">
<td><%= l(:field_column_names) %></td>
<td><%= render_query_columns_selection(@query) %></td>
</tr>
<tr>
<% if ['list', 'list_excerpt'].include?(deals_list_style) %>
<td><label for='group_by'><%= l(:field_group_by) %></label></td>
<td><%= select_tag('group_by',
options_for_select(
[[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
@query.group_by)
) %></td>
<% end %>
<% if deals_list_style == 'list_excerpt' %>
<td><label for='sort'><%= l(:label_sort) %></label></td>
<td><%= select_tag('sort',
options_for_select(
[[]] +
[[l(:field_contact_status), "status:asc"],
[l(:field_due_date), "due_date"],
[l(:field_created_on), "created_on:desc"],
[l(:field_updated_on), "updated_on:desc"]],
params[:sort])
) %></td>
<% end %>
</tr>
<%= call_hook(:view_deals_index_query_options, :deals => @deals, :query => @query) %>
<tr>
<td><label for='deals_list_style'><%= l(:label_crm_list_partial_style) %></label></td>
<td><%= select_tag('deals_list_style', options_for_select(deal_list_styles_for_select, deals_list_style)) %></td>
</tr>
</table>
</div>
</fieldset>
</div>
<%= render :partial => "crm_calendars/buttons" if deals_list_style == 'crm_calendars/crm_calendar' %>
<p class="buttons hide-when-print">
<%= link_to_function l(:button_apply), 'submit_query_form("query_form")', :class => 'icon icon-checked' %>
<%= link_to l(:button_clear), { :set_filter => 1, :project_id => @project }, :class => 'icon icon-reload' %>
<% if @query.new_record? && User.current.allowed_to?(:save_contacts_queries, @project, :global => true) %>
<%= link_to_function l(:button_save),
"$('#query_form').attr('action', '#{ @project ? new_project_crm_query_path(@project) : new_crm_query_path }'); submit_query_form('query_form')",
:class => 'icon icon-save' %>
<% end %>
</p>
<% end %>
<%= error_messages_for 'query' %>
<% if @query.valid? %>
<div id="contact_list" class="deal_list">
<% if @deals.empty? %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% else %>
<%= render :partial => deals_list_style %>
<span class="pagination"><%= pagination_links_full @deals_pages, @deals_count %></span>
<% end %>
</div>
<% other_formats_links do |f| %>
<%= f.link_to 'CSV', :url => filtered_params %>
<% end if User.current.allowed_to?(:export_contacts, @project, :global => true) %>
<% end %>
<% if Redmine::VERSION.to_s >= '3.4' || RedmineContacts.unstable_branch? %>
<%= context_menu %>
<% else %>
<%= context_menu url_for( {:controller => "deals", :action => "context_menu"} ) %>
<% end %>
<% content_for :sidebar do %>
<%= call_hook(:view_deals_sidebar_top, :deals => @deals) %>
<%= render :partial => 'common/sidebar' %>
<%= render :partial => 'deals_statistics' %>
<%= render_sidebar_crm_queries('deal') %>
<%= call_hook(:view_deals_sidebar_after_statistics, :deals => @deals) %>
<%= render :partial => 'notes/last_notes', :object => @last_notes %>
<%= render :partial => 'common/recently_viewed' %>
<%= call_hook(:view_deals_sidebar_bottom, :deals => @deals) %>
<% end unless (deals_list_style == 'list_board') %>
<% content_for(:header_tags) do %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
<meta name = "format-detection" content = "telephone=no">
<% end %>
@@ -0,0 +1,33 @@
<h2><%= l(:label_crm_deal_new) %></h2>
<%= labelled_form_for :deal, @deal, :url => {:action => 'create', :project_id => @project}, :html => {:id => "deal_form"} do |f| %>
<%= error_messages_for 'deal' %>
<%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %>
<div class="box tabular">
<div id="all_attributes">
<%= render :partial => 'form', :locals => {:f => f} %>
</div>
<% if false && @deal.safe_attribute?('watcher_user_ids') -%>
<p id="watchers_form"><label><%= l(:label_issue_watchers) %></label>
<span id="watchers_inputs">
<%= watchers_checkboxes(@deal, @available_watchers) %>
</span>
<span class="search_for_watchers">
<%= link_to l(:label_search_for_watchers),
{:controller => 'watchers', :action => 'new', :project_id => @deal.project},
:remote => true,
:method => 'get' %>
</span>
</p>
<% end %>
</div>
<%= submit_tag l(:button_save) -%>
<%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
<% end -%>
<% content_for :header_tags do %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<%= robot_exclusion_tag %>
<% end %>
@@ -0,0 +1,45 @@
api.deal do
api.id @deal.id
api.name @deal.name
api.price @deal.price
api.currency @deal.currency
api.price_type @deal.price_type
api.duration @deal.duration
api.probability @deal.probability
api.due_date @deal.due_date
api.background @deal.background
api.project(:id => @deal.project_id, :name => @deal.project.name) unless @deal.project.nil?
api.status(:id => @deal.status_id, :name => @deal.status.name) unless @deal.status.nil?
api.category(:id => @deal.category_id, :name => @deal.category.name) unless @deal.category.nil?
api.author(:id => @deal.author_id, :name => @deal.author.name) unless @deal.author.nil?
api.contact(:id => @deal.contact_id, :name => @deal.contact.name) unless @deal.contact.nil?
api.assigned_to(:id => @deal.assigned_to_id, :name => @deal.assigned_to.name) unless @deal.assigned_to.nil?
render_api_custom_values @deal.custom_field_values, api
api.created_on @deal.created_on
api.updated_on @deal.updated_on
api.array :related_contacts do
@deal.related_contacts.each do |contact|
api.contact(:id => contact.id, :name => contact.name)
end
end if @deal.related_contacts.any?
if authorize_for(:notes, :show)
api.array :notes do
@deal.notes.each do |note|
api.note do
api.id note.id
api.content note.content
api.type_id note.type_id
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 include_in_api_response?('notes') && @deal.notes.present? && User.current.allowed_to?(:view_deals, @project)
end
call_hook(:api_deals_show)
end
@@ -0,0 +1,129 @@
<div class="contextual">
<%= watcher_link(@deal, User.current) %>
<%= link_to l(:button_edit), edit_deal_path(@deal), :class => 'icon icon-edit' if User.current.allowed_to?(:edit_deals, @project) %>
<%= link_to l(:button_duplicate), new_project_deal_path(@project, :copy_from => @deal), :class => 'icon icon-duplicate' if User.current.allowed_to?(:add_deals, @project) %>
<%= link_to l(:button_delete), deal_path(@deal), :data => {:confirm => l(:text_are_you_sure)}, :method => :delete, :class => 'icon icon-del' if User.current.allowed_to?(:delete_deals, @project) %>
</div>
<h2><%= "#{l(:label_deal)} ##{@deal.id}" %></h2>
<div class="deal details">
<table class="subject_header">
<tr>
<td class="avatar"><%= avatar_to(@deal, :size => "64") %></td>
<td class="name" style="vertical-align: top;">
<h1><%= @deal.contact.name + ": " if @deal.contact %> <%= @deal.name %></h1>
<p class="author">
<%= authoring @deal.created_on, @deal.author %>.
<% if @deal.created_on != @deal.updated_on %>
<%= l(:label_updated_time, time_tag(@deal.updated_on)).html_safe %>.
<% end %>
</p>
<p><%= @deal.category %></p>
<% if @deal.status && @project.deal_statuses.any? %>
<div id="deal-status">
<%= deal_status_tag(@deal.status) %>
<% if authorize_for('deals', 'edit') %>
<span class="contextual">
<%= link_to l(:label_crm_deal_change_status), {}, :onclick => "$('#edit_status_form').show(); $('#deal-status').hide(); return false;", :id => 'edit_status_link' %>
</span>
<% end %>
</div>
<%= form_tag( {:controller => 'deals',
:action => 'update',
:project_id => @project,
:id => @deal },
:method => :put,
:multipart => true,
:id => "edit_status_form",
:style => "display:none; size: 100%" ) do %>
<%= select :deal, :status_id, options_for_select(collection_for_status_select, @deal.status_id.to_s), { :include_blank => false } %>
<%= submit_tag l(:button_save), :class => "button-small" %>
<%= link_to l(:button_cancel), {}, :onclick => "$('#edit_status_form').hide(); $('#deal-status').show(); return false;" %>
<br>
<% end %>
<% end %>
</td>
<% if !@deal.price.blank? || !@deal.due_date.blank? || !@deal.probability.blank? %>
<td class="subject_info">
<ul>
<% if !@deal.price.blank? %>
<li class="price icon <%= deal_currency_icon(@deal.currency) %>" title="Price"><%= @deal.price_to_s %></li>
<% end %>
<% if !@deal.due_date.blank? %>
<li class="price icon icon-date" title="Due date"><%= format_date(@deal.due_date) %></li>
<% end %>
<% if !@deal.probability.blank? %>
<li class="price icon icon-rosette" title="Probability"><%= @deal.probability %>%</li>
<% end %>
</ul>
</td>
<% end %>
</tr>
</table>
<% if RedmineContacts.products_plugin_installed? %>
<% if @deal.lines.present? %>
<% @lines_name = :label_deal_items %>
<%= render :partial => 'shared/product_lines', :locals => { :parent_object => @deal, :total_price => @deal.price_to_s } %>
<% end %>
<% end %>
<%= call_hook(:view_deals_show_details_bottom, {:deal => @deal }) %>
<% if authorize_for('notes', 'create') %>
<hr />
<%= render :partial => 'notes/add', :locals => {:note_source => @deal} %>
<% end %>
</div>
<div id="comments">
<h3><%= l(:label_crm_note_plural) %></h3>
<div id="notes">
<% @deal_events.each do |deal_event| %>
<% if deal_event[:object].is_a?(DealNote) %>
<%= render :partial => 'notes/note_item', :object => deal_event[:object], :locals => {:note_source => @deal} %>
<% end %>
<% if deal_event[:object].is_a?(DealProcess) %>
<%= render :partial => 'process_item', :object => deal_event[:object], :locals => {:note_source => @deal} %>
<% end %>
<% end %>
</div>
</div>
<% content_for :sidebar do %>
<%= render :partial => 'common/sidebar' %>
<%= call_hook(:view_deals_sidebar_top, :deal => @deal) %>
<%= render :partial => 'attributes' %>
<%= render :partial => 'common/responsible_user', :object => @deal %>
<%= render :partial => 'deal_contacts/contacts' %>
<%= render :partial => 'deals_issues/issues' %>
<%= render :partial => 'common/notes_attachments', :object => @deal_attachments %>
<% if !@deal.background.blank? %>
<h3><%= l(:label_crm_background_info) %></h3>
<div class="wiki"><%= textilizable(@deal, :background) %></div>
<% end %>
<% if User.current.allowed_to?(:add_issue_watchers, @project) ||
(@deal.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
<div id="watchers">
<%= render :partial => 'watchers/watchers', :locals => {:watched => @deal} %>
</div>
<% end %>
<%= render :partial => 'common/recently_viewed' %>
<%= call_hook(:view_deals_sidebar_bottom, :deal => @deal) %>
<% end %>
<% html_title "#{l(:label_deal)} ##{@deal.id}: #{@deal.name}" %>
<% content_for :header_tags do %>
<%= javascript_include_tag :contacts, :plugin => 'redmine_contacts' %>
<%= stylesheet_link_tag :contacts_sidebar, :plugin => 'redmine_contacts' %>
<meta name = "format-detection" content = "telephone=no">
<% end %>
@@ -0,0 +1 @@
$('#all_attributes').html('<%= escape_javascript(render :partial => 'form') %>');
@@ -0,0 +1,2 @@
$('.total').html('<%= escape_javascript(render :partial => 'board_total') %>');
$('.deals_counts').html('<%= escape_javascript(render :partial => 'board_deals_counts') %>');