Initial Redmine tooling and local plugin forks
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
class Deal < ActiveRecord::Base
|
||||
unloadable
|
||||
include Redmine::SafeAttributes
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
|
||||
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
|
||||
belongs_to :category, :class_name => 'DealCategory', :foreign_key => 'category_id'
|
||||
belongs_to :contact
|
||||
belongs_to :status, :class_name => 'DealStatus', :foreign_key => 'status_id'
|
||||
has_many :deals, :class_name => 'Deal', :foreign_key => 'reference_id'
|
||||
has_many :notes, :as => :source, :class_name => 'DealNote', :dependent => :delete_all
|
||||
has_many :deal_processes, :dependent => :delete_all
|
||||
has_many :deals_issues, :dependent => :destroy
|
||||
has_many :issues, :through => :deals_issues
|
||||
|
||||
if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
|
||||
has_many :lines, :class_name => 'ProductLine', :as => :container, :dependent => :delete_all
|
||||
has_many :products, :through => :lines, :uniq => true, :select => "#{Product.table_name}.*, #{ProductLine.table_name}.position"
|
||||
|
||||
accepts_nested_attributes_for :lines, :allow_destroy => true
|
||||
safe_attributes 'lines_attributes'
|
||||
acts_as_priceable :amount, :tax_amount, :subtotal, :total
|
||||
|
||||
before_validation :assign_lines
|
||||
before_save :calculate_price
|
||||
end
|
||||
|
||||
if ActiveRecord::VERSION::MAJOR >= 4
|
||||
has_and_belongs_to_many :related_contacts, lambda { order("#{Contact.table_name}.last_name, #{Contact.table_name}.first_name") }, :uniq => true, :class_name => 'Contact'
|
||||
else
|
||||
has_and_belongs_to_many :related_contacts, :order => "#{Contact.table_name}.last_name, #{Contact.table_name}.first_name", :uniq => true, :class_name => 'Contact'
|
||||
end
|
||||
|
||||
scope :visible, lambda {|*args|
|
||||
joins(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_deals, *args))
|
||||
}
|
||||
scope :by_project, lambda { |project_id| where(:project_id => project_id) unless project_id.blank? }
|
||||
scope :deletable, lambda { |*args| joins(:project).where(Project.allowed_to_condition(args.first || User.current, :delete_deals)) }
|
||||
|
||||
scope :live_search, lambda { |search| where("(#{Deal.table_name}.name LIKE ?)", "%#{search}%") }
|
||||
scope :live_search_with_contact, ->(search) do
|
||||
conditions = []
|
||||
values = {}
|
||||
search.split(' ').each_with_index { |word, index|
|
||||
key = :"v#{index}"
|
||||
conditions << "LOWER(#{Deal.table_name}.name) LIKE LOWER(:#{key})"
|
||||
conditions << "LOWER(#{Contact.table_name}.first_name) LIKE LOWER(:#{key})"
|
||||
conditions << "LOWER(#{Contact.table_name}.last_name) LIKE LOWER(:#{key})"
|
||||
conditions << "LOWER(#{Contact.table_name}.company) LIKE LOWER(:#{key})"
|
||||
conditions << "LOWER(#{Contact.table_name}.email) LIKE LOWER(:#{key})"
|
||||
values[key] = "%#{word}%"
|
||||
}
|
||||
sql = conditions.join(' OR ')
|
||||
joins(:contact).where(sql, values)
|
||||
end
|
||||
|
||||
scope :open, lambda { joins(:status).where("(#{DealStatus.table_name}.status_type = ? OR #{DealStatus.table_name}.status_type IS NULL)", DealStatus::OPEN_STATUS) }
|
||||
scope :closed, lambda { joins(:status).where("#{DealStatus.table_name}.status_type <> ?", DealStatus::OPEN_STATUS) }
|
||||
scope :won, lambda { joins(:status).where("#{DealStatus.table_name}.status_type = ?", DealStatus::WON_STATUS) }
|
||||
scope :lost, lambda { joins(:status).where("#{DealStatus.table_name}.status_type = ?", DealStatus::LOST_STATUS) }
|
||||
scope :was_in_status, lambda { |status_id| joins(:deal_processes).where(["#{DealProcess.table_name}.old_value = ? OR #{DealProcess.table_name}.value = ?", status_id, status_id]).uniq }
|
||||
scope :with_status, lambda { |status_id| where(:status_id => status_id) }
|
||||
|
||||
acts_as_priceable :price, :expected_revenue
|
||||
acts_as_customizable
|
||||
acts_as_viewable
|
||||
acts_as_watchable
|
||||
acts_as_attachable :view_permission => :view_deals,
|
||||
:delete_permission => :edit_deals
|
||||
|
||||
acts_as_event :datetime => :created_on,
|
||||
:url => Proc.new { |o| { :controller => 'deals', :action => 'show', :id => o } },
|
||||
:type => 'icon icon-add-deal',
|
||||
:title => Proc.new { |o| o.name },
|
||||
:description => Proc.new { |o| [o.price_to_s, o.contact ? o.contact.name : nil, o.background].join(' ').strip }
|
||||
|
||||
if ActiveRecord::VERSION::MAJOR >= 4
|
||||
acts_as_activity_provider :type => 'deals',
|
||||
:permission => :view_deals,
|
||||
:author_key => :author_id,
|
||||
:scope => joins(:project)
|
||||
|
||||
acts_as_searchable :columns => ["#{table_name}.name",
|
||||
"#{table_name}.background",
|
||||
"#{DealNote.table_name}.content"],
|
||||
:scope => includes([:project, :notes]),
|
||||
:date_column => :created_on
|
||||
else
|
||||
acts_as_activity_provider :type => 'deals',
|
||||
:permission => :view_deals,
|
||||
:author_key => :author_id,
|
||||
:find_options => { :include => :project }
|
||||
|
||||
acts_as_searchable :columns => ["#{table_name}.name",
|
||||
"#{table_name}.background",
|
||||
"#{DealNote.table_name}.content"],
|
||||
:include => [:project, :notes],
|
||||
:order_column => "#{table_name}.id"
|
||||
end
|
||||
|
||||
validates_presence_of :name, :project, :status
|
||||
validates_numericality_of :price, :allow_nil => true
|
||||
|
||||
after_update :create_deal_process
|
||||
after_create :send_notification
|
||||
|
||||
attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
|
||||
safe_attributes 'name',
|
||||
'background',
|
||||
'currency',
|
||||
'price',
|
||||
'price_type',
|
||||
'duration',
|
||||
'project_id',
|
||||
'author_id',
|
||||
'assigned_to_id',
|
||||
'status_id',
|
||||
'contact_id',
|
||||
'category_id',
|
||||
'probability',
|
||||
'due_date',
|
||||
'custom_field_values',
|
||||
'custom_fields',
|
||||
'watcher_user_ids',
|
||||
:if => lambda { |deal, user| deal.new_record? || user.allowed_to?(:edit_deals, deal.project) }
|
||||
|
||||
def initialize(attributes = nil, *args)
|
||||
super
|
||||
return unless new_record?
|
||||
# set default values for new records only
|
||||
self.status_id = DealStatus.default.try(:id)
|
||||
self.currency ||= ContactsSetting.default_currency
|
||||
end
|
||||
|
||||
def avatar
|
||||
end
|
||||
|
||||
def expected_revenue
|
||||
probability ? (probability.to_f / 100) * price.to_f : price
|
||||
end
|
||||
|
||||
def full_name
|
||||
result = ''
|
||||
result << contact.name + ': ' unless contact.blank?
|
||||
result << name
|
||||
end
|
||||
|
||||
def all_contacts
|
||||
@all_contacts ||= ([contact] + related_contacts).uniq
|
||||
end
|
||||
|
||||
def self.available_users(prj = nil)
|
||||
cond = '(1=1)'
|
||||
cond << " AND #{Deal.table_name}.project_id = #{prj.id}" if prj
|
||||
User.active.select("DISTINCT #{User.table_name}.*").
|
||||
joins("JOIN #{Deal.table_name} ON #{Deal.table_name}.assigned_to_id = #{User.table_name}.id").
|
||||
where(cond).
|
||||
order("#{User.table_name}.lastname, #{User.table_name}.firstname")
|
||||
end
|
||||
|
||||
def open?
|
||||
status.blank? || status.is_open?
|
||||
end
|
||||
|
||||
def init_deal_process(author)
|
||||
@current_deal_process ||= DealProcess.new(:deal => self, :author => (author || User.current))
|
||||
@deal_status_before_change = new_record? ? nil : status_id
|
||||
@current_deal_process
|
||||
end
|
||||
|
||||
def create_deal_process
|
||||
if @current_deal_process && @deal_status_before_change && !(@deal_status_before_change == status_id)
|
||||
@current_deal_process.old_value = @deal_status_before_change
|
||||
@current_deal_process.value = status_id
|
||||
@current_deal_process.save
|
||||
init_deal_process @current_deal_process.author
|
||||
end
|
||||
end
|
||||
|
||||
def visible?(usr = nil)
|
||||
(usr || User.current).allowed_to?(:view_deals, project)
|
||||
end
|
||||
|
||||
def editable?(usr = nil)
|
||||
(usr || User.current).allowed_to?(:edit_deals, project)
|
||||
end
|
||||
|
||||
def destroyable?(usr = nil)
|
||||
(usr || User.current).allowed_to?(:delete_deals, project)
|
||||
end
|
||||
|
||||
# Returns an array of projects that user can move deal to
|
||||
def self.allowed_target_projects(user = User.current)
|
||||
Project.where(Project.allowed_to_condition(user, :add_deals))
|
||||
end
|
||||
|
||||
# Returns the mail adresses of users that should be notified
|
||||
def recipients
|
||||
notified = []
|
||||
# Author and assignee are always notified unless they have been
|
||||
# locked or don't want to be notified
|
||||
notified << author if author
|
||||
if assigned_to
|
||||
notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
|
||||
end
|
||||
|
||||
notified += project.notified_users
|
||||
notified = notified.select { |u| u.active? }
|
||||
notified.uniq!
|
||||
# Remove users that can not view the contact
|
||||
notified.reject! { |user| !visible?(user) }
|
||||
notified.collect(&:mail)
|
||||
end
|
||||
|
||||
def status_was
|
||||
if status_id_changed? && status_id_was.present?
|
||||
@status_was ||= DealStatus.find_by_id(status_id_was)
|
||||
end
|
||||
end
|
||||
|
||||
def copy_from(arg)
|
||||
deal = arg.is_a?(Deal) ? arg : Deal.visible.find(arg)
|
||||
self.attributes = deal.attributes.dup.except('id', 'created_at', 'updated_at')
|
||||
self.custom_field_values = deal.custom_field_values.inject({}) { |h, v| h[v.custom_field_id] = v.value ; h }
|
||||
if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
|
||||
deal.lines.each do |line|
|
||||
lines.build(line.attributes)
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def contact_country
|
||||
try(:contact).try(:address).try(:country)
|
||||
end
|
||||
|
||||
def contact_city
|
||||
try(:contact).try(:address).try(:city)
|
||||
end
|
||||
|
||||
if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
|
||||
def has_taxes?
|
||||
!lines.map(&:tax).all? { |t| t == 0 || t.blank? }
|
||||
end
|
||||
|
||||
def has_discounts?
|
||||
!lines.map(&:discount).all? { |t| t == 0 || t.blank? }
|
||||
end
|
||||
|
||||
def tax_amount
|
||||
lines.select { |l| !l.marked_for_destruction? }.inject(0) { |sum, l| sum + l.tax_amount }.to_f
|
||||
end
|
||||
|
||||
def subtotal
|
||||
lines.select { |l| !l.marked_for_destruction? }.inject(0) { |sum, l| sum + l.total }.to_f
|
||||
end
|
||||
|
||||
def total_units
|
||||
lines.inject(0) { |sum, l| sum + (l.product.blank? ? 0 : l.quantity) }
|
||||
end
|
||||
|
||||
def calculate_price
|
||||
return true if lines.select { |l| !l.marked_for_destruction? }.empty?
|
||||
self.price = subtotal + (ContactsSetting.tax_exclusive? ? tax_amount : 0)
|
||||
end
|
||||
end
|
||||
|
||||
def info
|
||||
result = ''
|
||||
result = status.name if status
|
||||
result = result + ' - ' + price_to_s unless price.blank?
|
||||
result.html_safe
|
||||
end
|
||||
private
|
||||
|
||||
def send_notification
|
||||
Mailer.crm_deal_add(self).deliver if Setting.notified_events.include?('crm_deal_added')
|
||||
end
|
||||
|
||||
if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
|
||||
def assign_lines
|
||||
return unless new_record?
|
||||
lines.each { |l| l.container = self }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user