Modelos hechos

git-svn-id: https://192.168.0.254/svn/Rodax.redmine_rodax_crm/trunk@3 ff88604e-da85-c949-a72f-fc3aa3ba3724
This commit is contained in:
David Arranz 2012-01-03 19:11:27 +00:00
parent 946162f5d0
commit 2b75d1e4cf
27 changed files with 454 additions and 1 deletions

View File

@ -0,0 +1,2 @@
module ContractCategoriesHelper
end

120
app/models/contract.rb Normal file
View File

@ -0,0 +1,120 @@
class Contract < ActiveRecord::Base
unloadable
belongs_to :project
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
belongs_to :category, :class_name => 'ContractCategory', :foreign_key => 'category_id'
belongs_to :contact
belongs_to :status, :class_name => "ContractStatus", :foreign_key => "status_id"
has_many :contracts, :class_name => "contract", :foreign_key => "reference_id"
has_many :notes, :as => :source, :class_name => 'ContractNote', :dependent => :delete_all, :order => "created_on DESC"
has_many :contract_processes, :dependent => :delete_all
has_and_belongs_to_many :related_contacts, :class_name => 'Contact', :order => "#{Contact.table_name}.last_name, #{Contact.table_name}.first_name", :uniq => true
named_scope :visible, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :view_contracts)} }
named_scope :deletable, lambda {|*args| { :include => :project,
:conditions => Project.allowed_to_condition(args.first || User.current, :delete_contracts) }}
named_scope :live_search, lambda {|search| {:conditions => ["(#{Contract.table_name}.name LIKE ?)", "%#{search}%"] }}
named_scope :open, :include => :status, :conditions => ["#{ContractStatus.table_name}.is_closed = ?", false]
acts_as_customizable
acts_as_viewable
acts_as_watchable
acts_as_attachable :view_permission => :view_contracts,
:delete_permission => :edit_contracts
acts_as_event :datetime => :created_on,
:url => Proc.new {|o| {:controller => 'contracts', :action => 'show', :id => o}},
:type => 'icon-report',
:title => Proc.new {|o| o.name },
:description => Proc.new {|o| [o.price, o.contact ? o.contact.name : nil, o.background].join(' ').strip }
acts_as_searchable :columns => ["#{table_name}.name",
"#{table_name}.background"],
:include => [:project],
# sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id"
validates_presence_of :name
validates_numericality_of :price, :allow_nil => true
after_save :create_contract_process
include ActionView::Helpers::NumberHelper
include ::ContractsHelper
def after_initialize
if new_record?
# set default values for new records only
self.status ||= ContractStatus.default
end
end
def avatar
end
def full_name
result = ''
result << self.contact.name + ": " unless self.contact.blank?
result << self.name
end
def all_contacts
@all_contacts ||= ([self.contact] + self.related_contacts ).uniq
end
def self.available_users(prj=nil)
cond = "(1=1)"
cond << " AND #{Contract.table_name}.project_id = #{prj.id}" if prj
User.active.find(:all, :select => "DISTINCT #{User.table_name}.*", :joins => "JOIN #{Contract.table_name} ON #{Contract.table_name}.assigned_to_id = #{User.table_name}.id", :conditions => cond, :order => "#{User.table_name}.lastname, #{User.table_name}.firstname")
end
def init_contract_process(author)
@current_contract_process ||= ContractProcess.new(:contract => self, :author => (author || User.current))
@contract_status_before_change = self.new_record? ? nil : self.status_id
updated_on_will_change!
@current_contract_process
end
def create_contract_process
if @current_contract_process && !(@contract_status_before_change == self.status_id)
@current_contract_process.old_value = @contract_status_before_change
@current_contract_process.value = self.status_id
@current_contract_process.save
# reset current journal
init_contract_process @current_contract_process.author
end
end
def visible?(usr=nil)
(usr || User.current).allowed_to?(:view_contracts, self.project)
end
def editable?(usr=nil)
(usr || User.current).allowed_to?(:edit_contracts, self.project)
end
def destroyable?(usr=nil)
(usr || User.current).allowed_to?(:delete_contracts, self.project)
end
def info
result = ""
result = self.status.name if self.status
result = result + " - " + contract_price(self) if !self.price.blank?
result
end
end

View File

@ -0,0 +1,26 @@
class ContractCategory < ActiveRecord::Base
unloadable
belongs_to :project
has_many :contracts, :foreign_key => 'category_id', :dependent => :nullify
validates_presence_of :name
validates_uniqueness_of :name, :scope => [:project_id]
validates_length_of :name, :maximum => 30
alias :destroy_without_reassign :destroy
# Destroy the category
# If a category is specified, issues are reassigned to this category
def destroy(reassign_to = nil)
if reassign_to && reassign_to.is_a?(ContractCategory) && reassign_to.project == self.project
Contract.update_all("category_id = #{reassign_to.id}", "category_id = #{id}")
end
destroy_without_reassign
end
def <=>(category)
name <=> category.name
end
def to_s; name end
end

View File

@ -0,0 +1,7 @@
class ContractCustomField < ActiveRecord::Base
unloadable
def type_name
:label_contract_plural
end
end

View File

@ -0,0 +1,23 @@
class ContractNote < Note
unloadable
belongs_to :contract, :foreign_key => :source_id
acts_as_searchable :columns => ["#{table_name}.content"],
:include => [:contract => :project],
:project_key => "#{Project.table_name}.id",
:permission => :view_contracts,
# sort by id so that limited eager loading doesn't break with postgresql
:order_column => "#{table_name}.id"
acts_as_activity_provider :type => 'contacts',
:permission => :view_contracts,
:author_key => :author_id,
:find_options => {:include => [:contract => :project],
:conditions => {:source_type => 'Contract'}}
named_scope :visible, lambda {|*args| { :include => [:contract => :project],
:conditions => Project.allowed_to_condition(args.first || User.current, :view_contracts) +
" AND (#{ContractNote.table_name}.source_type = 'Contract')"} }
end

View File

@ -0,0 +1,7 @@
class ContractProcess < ActiveRecord::Base
unloadable
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
belongs_to :contract
end

View File

@ -0,0 +1,78 @@
class ContractStatus < ActiveRecord::Base
unloadable
has_and_belongs_to_many :projects
has_many :contracts, :foreign_key => 'status_id', :dependent => :nullify
acts_as_list
validates_presence_of :name
validates_uniqueness_of :name
validates_length_of :name, :maximum => 30
validates_format_of :name, :with => /^[\w\s\'\-]*$/i
def after_save
ContractStatus.update_all("is_default=#{connection.quoted_false}", ['id <> ?', id]) if self.is_default?
end
# Returns the default status for new Contracts
def self.default
find(:first, :conditions =>["is_default=?", true])
end
# Returns an array of all statuses the given role can switch to
# Uses association cache when called more than one time
def new_statuses_allowed_to(roles, tracker)
if roles && tracker
role_ids = roles.collect(&:id)
new_statuses = workflows.select {|w| role_ids.include?(w.role_id) && w.tracker_id == tracker.id}.collect{|w| w.new_status}.compact.sort
else
[]
end
end
# Same thing as above but uses a database query
# More efficient than the previous method if called just once
def find_new_statuses_allowed_to(roles, tracker)
if roles && tracker
workflows.find(:all,
:include => :new_status,
:conditions => { :role_id => roles.collect(&:id),
:tracker_id => tracker.id}).collect{ |w| w.new_status }.compact.sort
else
[]
end
end
def new_status_allowed_to?(status, roles, tracker)
if status && roles && tracker
!workflows.find(:first, :conditions => {:new_status_id => status.id, :role_id => roles.collect(&:id), :tracker_id => tracker.id}).nil?
else
false
end
end
def color_name
return "#" + "%06x" % self.color unless self.color.nil?
end
def color_name=(clr)
self.color = clr.from(1).hex
end
def <=>(status)
position <=> status.position
end
def to_s; name end
private
def check_integrity
raise "Can't delete status" if Contract.find(:first, :conditions => ["status_id=?", self.id])
end
# Deletes associated workflows
def delete_workflows
Workflow.delete_all(["old_status_id = :id OR new_status_id = :id", {:id => id}])
end
end

View File

@ -162,4 +162,7 @@ es:
field_age: Age field_age: Age
label_vcf_import: Import from vCard label_vcf_import: Import from vCard
label_mail_from: From label_mail_from: From
permission_import_contacts: Import contacts permission_import_contacts: Import contacts
#2.2.2-rodax
label_contract_plural: Categorías de contratos

View File

@ -0,0 +1,31 @@
class CreateContracts < ActiveRecord::Migration
def self.up
create_table :contracts 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
t.datetime :start_date
t.datetime :end_date
end
add_index :contracts, :contact_id
add_index :contracts, :status_id
add_index :contracts, :author_id
add_index :contracts, :category_id
end
def self.down
drop_table :contracts
end
end

View File

@ -0,0 +1,2 @@
class CreateContractNotes < ActiveRecord::Migration
end

View File

@ -0,0 +1,13 @@
class CreateContractCategories < ActiveRecord::Migration
def self.up
create_table :contract_categories do |t|
t.string :name, :null => false
t.integer :project_id
end
add_index :contract_categories, :project_id
end
def self.down
drop_table :contract_categories
end
end

View File

@ -0,0 +1,2 @@
class CreateContractCustomFields < ActiveRecord::Migration
end

View File

@ -0,0 +1,19 @@
class CreateContractStatuses < ActiveRecord::Migration
def self.up
create_table :contract_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 :contract_statuses, [:is_closed]
ContractStatus.create(:name => "Pending", :is_closed => false, :is_default => true, :color => "AAAAAA".hex)
ContractStatus.create(:name => "Won", :is_closed => true, :is_default => false, :color => "008000".hex)
ContractStatus.create(:name => "Lost", :is_closed =>true, :is_default => false, :color => "FF0000".hex)
end
def self.down
drop_table :contract_statuses
end
end

View File

@ -0,0 +1,17 @@
class CreateContractProcesses < ActiveRecord::Migration
def self.up
create_table :contract_processes do |t|
t.integer :contract_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 :contract_processes, [:author_id]
add_index :contract_processes, [:contract_id]
end
def self.down
drop_table :contract_processes
end
end

View File

@ -0,0 +1,13 @@
class CreateContractStatusesProjects < ActiveRecord::Migration
def self.up
create_table :contract_statuses_projects, :id => false do |t|
t.integer :project_id, :default => 0, :null => false
t.integer :contract_status_id, :default => 0, :null => false
end
add_index :contract_statuses_projects, [:project_id, :contract_status_id]
end
def self.down
drop_table :contract_statuses_projects
end
end

5
test/fixtures/contract_categories.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

5
test/fixtures/contract_notes.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

5
test/fixtures/contract_processes.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

5
test/fixtures/contract_statuses.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

5
test/fixtures/contracts.yml vendored Normal file
View File

@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ContractCategoryTest < ActiveSupport::TestCase
fixtures :contract_categories
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ContractCustomFieldTest < ActiveSupport::TestCase
fixtures :contract_custom_fields
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ContractNoteTest < ActiveSupport::TestCase
fixtures :contract_notes
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ContractProcessTest < ActiveSupport::TestCase
fixtures :contract_processes
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ContractStatusTest < ActiveSupport::TestCase
fixtures :contract_statuses
# Replace this with your real tests.
def test_truth
assert true
end
end

View File

@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'
class ContractsTest < ActiveSupport::TestCase
fixtures :contracts
# Replace this with your real tests.
def test_truth
assert true
end
end