# This file is a part of Redmine CRM (redmine_contacts) plugin, # customer relationship management plugin for Redmine # # Copyright (C) 2010-2018 RedmineUP # http://www.redmineup.com/ # # redmine_contacts 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_contacts 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_contacts. If not, see . class DealsController < ApplicationController unloadable PRICE_TYPE_PULLDOWN = [l(:label_price_fixed_bid), l(:label_price_per_hour)] before_action :find_deal, :only => [:show, :edit, :update, :destroy] before_action :find_project, :only => [:new, :create, :update_form] before_action :bulk_find_deals, :only => [:bulk_update, :bulk_edit, :bulk_destroy, :context_menu] before_action :authorize, :except => [:index] before_action :find_optional_project, :only => [:index] before_action :update_deal_from_params, :only => [:edit, :update] before_action :build_new_deal_from_params, :only => [:new, :update_form] before_action :find_deal_attachments, :only => :show skip_before_filter :authorize, :only => :add_product_line if RedmineContacts.products_plugin_installed? accept_api_auth :index, :show, :create, :update, :destroy helper :attachments helper :timelog helper :watchers helper :custom_fields helper :context_menus helper :sort helper :crm_queries helper :notes helper :queries helper :calendars include QueriesHelper include CrmQueriesHelper include WatchersHelper include DealsHelper include SortHelper if RedmineContacts.products_plugin_installed? include ProductsHelper helper :products end def index retrieve_crm_query('deal') sort_init(@query.sort_criteria.empty? ? [['created_on', 'desc']] : @query.sort_criteria) sort_update(@query.sortable_columns) @query.sort_criteria = sort_criteria.to_a if @query.valid? case params[:format] when 'csv', 'pdf' @limit = Setting.issues_export_limit.to_i when 'atom' @limit = Setting.feeds_limit.to_i when 'xml', 'json' @offset, @limit = api_offset_and_limit else @limit = per_page_option end @deals_count = @query.object_count @deals_scope = @query.objects_scope @deal_amount = @query.deal_amount @deal_weighted_amount = @query.weighted_amount @deals_pages = Paginator.new @deals_count, @limit, params['page'] @offset ||= @deals_pages.offset @deal_count_by_group = @query.object_count_by_group @deals = @query.results_scope( :include => [{ :contact => [:avatar, :projects, :address] }, :author], :search => params[:search], :order => sort_clause, :limit => @limit, :offset => @offset ) if deals_list_style == 'crm_calendars/crm_calendar' retrieve_crm_calendar(:start_date_field => 'due_date') @calendar.events = @query.results_scope( :include => [:contact], :search => params[:search], :conditions => ['due_date BETWEEN ? AND ?', @calendar.startdt, @calendar.enddt] ) end respond_to do |format| format.html { request.xhr? ? render(:partial => deals_list_style, :layout => false) : last_notes } format.api format.atom { render_feed(@deals, :title => "#{@project || Setting.app_title}: #{l(:label_order_plural)}") } format.csv { send_data(deals_to_csv(@deals), :type => 'text/csv; header=present', :filename => 'deals.csv') } format.pdf { send_data(deals_to_pdf(@deals, @project, @query), :type => 'application/pdf', :filename => 'deals.pdf') } end else respond_to do |format| format.html { render(:template => 'deals/index', :layout => !request.xhr?) } format.any(:atom, :csv, :pdf) { render(:nothing => true) } format.api { render_validation_errors(@query) } end end rescue ActiveRecord::RecordNotFound render_404 end def show @note = DealNote.new(:created_on => Time.now) respond_to do |format| format.html do @deal_issues = @deal.issues.visible @deal.viewed @deal_events = (@deal.deal_processes.where("#{DealProcess.table_name}.old_value IS NOT NULL").includes([:to, :from, :author]) | @deal.notes.includes([:attachments, :author])).map{|o| {:date => o.is_a?(DealProcess) ? o.created_at : o.created_on, :author => o.author, :object => o} } @deal_events.sort! { |x, y| y[:date] <=> x[:date] } end format.api end end def new end def create @deal = Deal.new @deal.safe_attributes = params[:deal] @deal.project = @project @deal.author ||= User.current @deal.price = parsed_price(params[:deal][:price]) @deal.init_deal_process(User.current) if @deal.save flash[:notice] = l(:notice_successful_create) respond_to do |format| format.html { redirect_to(params[:continue] ? { :action => 'new' } : { :action => 'show', :id => @deal }) } format.api { render :action => 'show', :status => :created, :location => deal_url(@deal) } end else respond_to do |format| format.html { render :action => 'new' } format.api { render_validation_errors(@deal) } end end end def update @deal.init_deal_process(User.current) @deal.safe_attributes = params[:deal] if @deal.save # @deal.contacts = [Contact.find(params[:contacts])] if params[:contacts] retrieve_crm_query('deal') @deals_scope = @query.objects_scope flash[:notice] = l(:notice_successful_update) respond_to do |format| format.html { redirect_back_or_default(:action => 'show', :id => @deal) } format.api { render_api_ok } format.js { render :update_total } end else respond_to do |format| format.html { render :action => 'edit' } format.api { render_validation_errors(@deal) } format.js { render "alert('Error!')" } end end end def edit respond_to do |format| format.html {} format.xml {} end end def destroy if @deal.destroy flash[:notice] = l(:notice_successful_delete) respond_to do |format| format.html { redirect_to :action => 'index', :project_id => params[:project_id] } format.api { render_api_ok } end else flash[:error] = l(:notice_unsuccessful_save) end end def context_menu @deal = @deals.first if @deals.size == 1 @can = { :edit => User.current.allowed_to?(:edit_deals, @projects), :delete => User.current.allowed_to?(:delete_deals, @projects) } @back = back_url render :layout => false end def bulk_destroy @deals.each do |deal| begin deal.reload.destroy rescue ::ActiveRecord::RecordNotFound # raised by #reload if deal no longer exists # nothing to do, deal was already deleted (eg. by a parent) end end respond_to do |format| format.html { redirect_back_or_default(:action => 'index', :project_id => params[:project_id]) } format.api { head :ok } end end def bulk_edit @available_statuses = @projects.map(&:deal_statuses).inject { |memo, w| memo & w } @custom_fields = DealCustomField.order(:name) @available_categories = @projects.map(&:deal_categories).inject { |memo, w| memo & w } @assignables = @projects.map(&:assignable_users).inject { |memo, a| memo & a } end def bulk_update unsaved_deal_ids = [] @deals.each do |deal| deal.reload deal.init_deal_process(User.current) deal.safe_attributes = parse_params_for_bulk_deal_attributes(params) unless deal.save # Keep unsaved deal ids to display them in flash error unsaved_deal_ids << deal.id end if params[:note] && !params[:note][:content].blank? note = DealNote.new note.safe_attributes = params[:note] note.author = User.current deal.notes << note end end set_flash_from_bulk_contact_save(@deals, unsaved_deal_ids) redirect_back_or_default(:controller => 'deals', :action => 'index', :project_id => @project) end private def last_notes(count = 5) # TODO: Исправить говнокод этот и выделить все в плагин acts-as-noteble scope = DealNote.where({}) scope = scope.where("#{Deal.table_name}.project_id = ?", @project.id) if @project @last_notes = scope.visible.order("#{DealNote.table_name}.created_on DESC").limit(count) end def build_new_deal_from_params if params[:id].blank? @deal = Deal.new @deal.assigned_to_id = User.current.id @deal.name = params[:name] if params[:name] @deal.contact = Contact.find(params[:contact_id]) if params[:contact_id] if params[:copy_from] begin @copy_from = Deal.visible.find(params[:copy_from]) @deal.copy_from(@copy_from) rescue ActiveRecord::RecordNotFound render_404 return end end else @deal = Deal.visible.find(params[:id]) end @deal.project = @project @deal.author ||= User.current @deal.safe_attributes = params[:deal] @available_watchers = (@deal.project.users.sort + @deal.watcher_users).uniq end def update_deal_from_params end def update_form end def find_deal_attachments @deal_attachments = Attachment.where(:container_type => 'Note', :container_id => @deal.notes.map(&:id)).order(:created_on) end def bulk_find_deals @deals = Deal.where(:id => (params[:id] || params[:ids])).includes([:project, :contact]) raise ActiveRecord::RecordNotFound if @deals.empty? if @deals.detect { |deal| !deal.visible? } deny_access return end @projects = @deals.collect(&:project).compact.uniq @project = @projects.first if @projects.size == 1 rescue ActiveRecord::RecordNotFound render_404 end def find_deal @deal = Deal.where(:id => params[:id]).includes([:project, :status, :category]).first @project = @deal.project rescue ActiveRecord::RecordNotFound render_404 end def find_project(project_id = nil) project_id ||= (params[:deal] && params[:deal][:project_id]) || params[:project_id] @project = Project.find(project_id) rescue ActiveRecord::RecordNotFound render_404 end def parse_params_for_bulk_deal_attributes(params) attributes = (params[:deal] || {}).reject { |_k, v| v.blank? } attributes.keys.each { |k| attributes[k] = '' if attributes[k] == 'none' } attributes[:custom_field_values].reject! { |_k, v| v.blank? } if attributes[:custom_field_values] attributes end def parsed_price(price) return unless price price.gsub!(ContactsSetting.thousands_delimiter, '') price.gsub!(ContactsSetting.decimal_separator, '.') price.to_f end end