e9_crm 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +1 -0
  2. data/app/controllers/e9_crm/campaigns_controller.rb +3 -0
  3. data/app/controllers/e9_crm/contact_emails_controller.rb +5 -1
  4. data/app/controllers/e9_crm/contact_offers_controller.rb +3 -0
  5. data/app/controllers/e9_crm/deals_controller.rb +41 -13
  6. data/app/controllers/e9_crm/file_download_offers_controller.rb +3 -0
  7. data/app/controllers/e9_crm/leads_controller.rb +74 -0
  8. data/app/controllers/e9_crm/new_content_subscription_offers_controller.rb +3 -0
  9. data/app/controllers/e9_crm/newsletter_subscription_offers_controller.rb +3 -0
  10. data/app/controllers/e9_crm/offer_subclass_controller.rb +11 -0
  11. data/app/controllers/e9_crm/offers_controller.rb +37 -0
  12. data/app/controllers/e9_crm/resources_controller.rb +8 -0
  13. data/app/controllers/e9_crm/video_offers_controller.rb +3 -0
  14. data/app/helpers/e9_crm/deals_helper.rb +32 -0
  15. data/app/helpers/e9_crm/leads_helper.rb +63 -0
  16. data/app/helpers/e9_crm/offers_helper.rb +16 -0
  17. data/app/models/campaign.rb +1 -0
  18. data/app/models/contact.rb +2 -0
  19. data/app/models/contact_offer.rb +2 -0
  20. data/app/models/deal.rb +110 -8
  21. data/app/models/file_download_offer.rb +2 -0
  22. data/app/models/new_content_subscription_offer.rb +5 -0
  23. data/app/models/newsletter_subscription_offer.rb +5 -0
  24. data/app/models/no_campaign.rb +5 -1
  25. data/app/models/offer.rb +30 -19
  26. data/app/models/subscription_offer.rb +3 -0
  27. data/app/models/video_offer.rb +2 -0
  28. data/app/views/e9_crm/campaigns/_header.html.haml +2 -2
  29. data/app/views/e9_crm/contact_offers/_form_inner.html.haml +5 -0
  30. data/app/views/e9_crm/contacts/_form_inner.html.haml +2 -1
  31. data/app/views/e9_crm/contacts/_index_sidebar.html.haml +30 -0
  32. data/app/views/e9_crm/contacts/index.html.haml +1 -30
  33. data/app/views/e9_crm/contacts/index.js.erb +1 -1
  34. data/app/views/e9_crm/deals/_form_inner.html.haml +16 -8
  35. data/app/views/e9_crm/deals/_header.html.haml +13 -0
  36. data/app/views/e9_crm/deals/_leads_header.html.haml +12 -0
  37. data/app/views/e9_crm/deals/_leads_table.html.haml +34 -0
  38. data/app/views/e9_crm/deals/leads.html.haml +4 -9
  39. data/app/views/e9_crm/deals/leads.js.erb +1 -0
  40. data/app/views/e9_crm/deals/offer_form.html.haml +6 -0
  41. data/app/views/e9_crm/deals/reports.html.haml +1 -1
  42. data/app/views/e9_crm/{offers → file_download_offers}/_form.html.haml +0 -0
  43. data/app/views/e9_crm/file_download_offers/_form_inner.html.haml +11 -0
  44. data/app/views/e9_crm/leads/_form.html.haml +26 -0
  45. data/app/views/e9_crm/leads/create.js.erb +11 -0
  46. data/app/views/e9_crm/leads/new.html.haml +6 -0
  47. data/app/views/e9_crm/menu_options/_header.html.haml +3 -4
  48. data/app/views/e9_crm/new_content_subscription_offers/_form_inner.html.haml +1 -0
  49. data/app/views/e9_crm/newsletter_subscription_offers/_form_inner.html.haml +1 -0
  50. data/app/views/e9_crm/offers/_footer.html.haml +0 -0
  51. data/app/views/e9_crm/offers/_form_inner.html.haml +8 -22
  52. data/app/views/e9_crm/offers/_header.html.haml +12 -0
  53. data/app/views/e9_crm/offers/_offer.html.haml +4 -0
  54. data/app/views/e9_crm/offers/_public_offer.html.haml +6 -0
  55. data/app/views/e9_crm/offers/show.html.haml +2 -0
  56. data/app/views/e9_crm/resources/_form_inner.html.haml +0 -5
  57. data/app/views/e9_crm/resources/create.js.erb +3 -2
  58. data/app/views/e9_crm/resources/edit.html.haml +1 -1
  59. data/app/views/e9_crm/resources/new.html.haml +1 -1
  60. data/app/views/e9_crm/resources/show.html.haml +1 -1
  61. data/app/views/e9_crm/resources/update.js.erb +3 -1
  62. data/app/views/e9_crm/subscription_offers/_form_inner.html.haml +5 -0
  63. data/app/views/e9_crm/video_offers/_form_inner.html.haml +5 -0
  64. data/config/locales/e9.en.yml +3 -2
  65. data/config/locales/en.yml +30 -13
  66. data/config/routes.rb +31 -8
  67. data/e9_crm.gemspec +1 -1
  68. data/lib/e9_crm/controller.rb +94 -0
  69. data/lib/e9_crm/model.rb +9 -9
  70. data/lib/e9_crm/rails_extensions.rb +9 -0
  71. data/lib/e9_crm/tracking_controller.rb +48 -80
  72. data/lib/e9_crm/version.rb +1 -1
  73. data/lib/e9_crm.rb +8 -1
  74. data/lib/generators/e9_crm/templates/migration.rb +3 -9
  75. metadata +39 -9
  76. data/Gemfile.lock +0 -98
  77. data/app/models/affiliate.rb +0 -4
  78. data/app/models/sales_person.rb +0 -4
  79. data/app/views/e9_crm/contact_emails/send_email.js.erb +0 -1
  80. data/app/views/e9_crm/offers/_form_inner.html.haml.bak +0 -43
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .bundle
2
2
  *.swp
3
3
  pkg/*
4
+ Gemfile.lock
@@ -30,6 +30,9 @@ class E9Crm::CampaignsController < E9Crm::ResourcesController
30
30
  scope = scope.select(
31
31
  'campaigns.*, count(deals.id) won_deals_count, count(deals_campaigns.id) deals_count'
32
32
  ).joins([:deals, :won_deals])
33
+ else
34
+ # don't include NoCampaign normally
35
+ scope = scope.typed
33
36
  end
34
37
  scope
35
38
  end
@@ -6,7 +6,7 @@ class E9Crm::ContactEmailsController < E9Crm::ResourcesController
6
6
  def create
7
7
  create! do |success, failure|
8
8
  success.html { redirect_to :admin_sent_email }
9
- success.js { head :ok }
9
+ success.js
10
10
  end
11
11
  end
12
12
 
@@ -33,4 +33,8 @@ class E9Crm::ContactEmailsController < E9Crm::ResourcesController
33
33
  def template
34
34
  @_template ||= EmailTemplate.find(params[:etid])
35
35
  end
36
+
37
+ def determine_layout
38
+ request.xhr? ? false : super
39
+ end
36
40
  end
@@ -0,0 +1,3 @@
1
+ class E9Crm::ContactOffersController < E9Crm::OfferSubclassController
2
+ defaults :resource_class => ContactOffer
3
+ end
@@ -7,23 +7,49 @@ class E9Crm::DealsController < E9Crm::ResourcesController
7
7
 
8
8
  filter_access_to :leads, :reports, :require => :read, :context => :admin
9
9
 
10
+ skip_after_filter :flash_to_headers
11
+
10
12
  prepend_before_filter :set_leads_index_title, :only => :leads
11
13
  prepend_before_filter :set_reports_index_title, :only => :reports
12
14
 
13
15
  ##
14
- # Index/Reports Scopes
16
+ # All Scopes
15
17
  #
16
18
 
19
+ has_scope :until_time, :as => :until, :unless => 'params[:from].present?'
20
+
21
+ has_scope :from_time, :as => :from do |controller, scope, value|
22
+ if controller.params[:until]
23
+ scope.for_time_range(value, controller.params[:until])
24
+ else
25
+ scope.from_time(value)
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Leads Scopes
31
+ #
32
+
17
33
  # NOTE default => 'true' only exists to ensure this scope is called
18
34
  has_scope :only_leads, :only => :leads, :default => 'true' do |controller, scope|
19
35
  scope.leads(true)
20
36
  end
21
37
 
38
+ has_scope :offer, :only => :leads
39
+
40
+ ##
41
+ # Index Scopes
42
+ #
43
+
22
44
  # NOTE default => 'false' only exists to ensure this scope is called
23
- has_scope :no_leads, :except => [:leads, :reports], :default => 'false' do |controller, scope|
45
+ has_scope :no_leads, :only => :index, :default => 'false' do |controller, scope|
24
46
  scope.leads(false)
25
47
  end
26
48
 
49
+ has_scope :category, :only => :index
50
+ has_scope :status, :only => :index
51
+ has_scope :owner, :only => :index
52
+
27
53
  ##
28
54
  # Reports scopes
29
55
  #
@@ -38,15 +64,6 @@ class E9Crm::DealsController < E9Crm::ResourcesController
38
64
  scope & Campaign.of_type("#{value}_campaign".classify)
39
65
  end
40
66
 
41
- has_scope :until_time, :as => :until, :unless => 'params[:from].present?'
42
-
43
- has_scope :from_time, :as => :from do |controller, scope, value|
44
- if controller.params[:until]
45
- scope.for_time_range(value, controller.params[:until])
46
- else
47
- scope.from_time(value)
48
- end
49
- end
50
67
 
51
68
  ##
52
69
  # Actions
@@ -62,15 +79,26 @@ class E9Crm::DealsController < E9Crm::ResourcesController
62
79
 
63
80
  protected
64
81
 
82
+ def add_edit_breadcrumb(opts = {})
83
+ @edit_title = e9_t(resource.lead? ? :new_title : :edit_title)
84
+ add_breadcrumb!(@edit_title)
85
+ end
86
+
65
87
  def collection
66
88
  get_collection_ivar || begin
67
89
  set_collection_ivar(
68
90
  if params[:action] == 'reports'
69
91
  end_of_association_chain.all
70
- else
92
+
93
+ elsif params[:action] == 'deals'
94
+ # NOTE this is a pretty ugly join just to be able to sort on owner
71
95
  end_of_association_chain
72
96
  .joins("left outer join contacts on contacts.id = deals.contact_id")
73
97
  .select("deals.*, contacts.first_name owner_name")
98
+ .all
99
+
100
+ else
101
+ end_of_association_chain.includes(:contacts).paginate(pagination_parameters)
74
102
  end
75
103
  )
76
104
  end
@@ -85,6 +113,6 @@ class E9Crm::DealsController < E9Crm::ResourcesController
85
113
  end
86
114
 
87
115
  def ordered_if
88
- %w(index reports).member? params[:action]
116
+ %w(index leads reports).member? params[:action]
89
117
  end
90
118
  end
@@ -0,0 +1,3 @@
1
+ class E9Crm::FileDownloadOffersController < E9Crm::OfferSubclassController
2
+ defaults :resource_class => FileDownloadOffer
3
+ end
@@ -0,0 +1,74 @@
1
+ class E9Crm::LeadsController < ApplicationController
2
+ # TODO these should all be included in e9_base
3
+ include E9Rails::Helpers::ResourceLinks
4
+ include E9Rails::Helpers::Title
5
+ include E9Rails::Helpers::Translation
6
+ include E9Rails::Helpers::ResourceErrorMessages
7
+
8
+ inherit_resources
9
+ belongs_to :offer, :param => :public_offer_id
10
+ defaults :resource_class => Deal, :instance_name => 'deal'
11
+
12
+ respond_to :js
13
+ respond_to :html, :only => []
14
+
15
+ has_scope :leads, :type => :boolean, :default => true
16
+
17
+ # we want to control (or not use) our own flash messages
18
+ skip_after_filter :flash_to_headers
19
+
20
+ before_filter :association_chain
21
+ after_filter :install_offers_cookie, :only => :create
22
+
23
+ #
24
+ # In the case that this is actually an HTML request, redirect to
25
+ # the offer on success (regardless of what type of offer?)
26
+ #
27
+ def create
28
+ create!(:flash => false) do |success, failure|
29
+ success.html { redirect_to public_offer_path(@offer) }
30
+ success.js
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def create_resource(object)
37
+ @lead_was_created = object.save
38
+ end
39
+
40
+ def build_resource
41
+ get_resource_ivar || set_resource_ivar(
42
+ Deal.leads.new((params[resource_instance_name] || {}).reverse_merge(
43
+ :user => current_user,
44
+ :offer => @offer,
45
+ :campaign => tracking_campaign
46
+ ))
47
+ )
48
+ end
49
+
50
+ #
51
+ # this cookie is installed after successfully creating a lead and once installed,
52
+ # allows the cookied user to view the public offer page for the parent @offer
53
+ #
54
+ def install_offers_cookie
55
+ if @lead_was_created
56
+ cookied_offer_array = Marshal.load(cookies['_e9_offers']) rescue []
57
+
58
+ cookied_offer_array |= [@offer.id]
59
+
60
+ cookies['_e9_offers'] = {
61
+ :value => Marshal.dump(cookied_offer_array),
62
+ :expires => 1.year.from_now
63
+ }
64
+ end
65
+ end
66
+
67
+ def determine_layout
68
+ request.xhr? ? false : super
69
+ end
70
+
71
+ def find_current_page
72
+ @current_page ||= Offer.page || super
73
+ end
74
+ end
@@ -0,0 +1,3 @@
1
+ class E9Crm::NewContentSubscriptionOffersController < E9Crm::OfferSubclassController
2
+ defaults :resource_class => NewContentSubscriptionOffer
3
+ end
@@ -0,0 +1,3 @@
1
+ class E9Crm::NewsletterSubscriptionOffersController < E9Crm::OfferSubclassController
2
+ defaults :resource_class => NewsletterSubscriptionOffer
3
+ end
@@ -0,0 +1,11 @@
1
+ class E9Crm::OfferSubclassController < E9Crm::ResourcesController
2
+ protected
3
+
4
+ def add_index_breadcrumb
5
+ add_breadcrumb! Offer.model_name.collection.titleize, offers_path
6
+ end
7
+
8
+ def determine_layout
9
+ request.xhr? ? false : super
10
+ end
11
+ end
@@ -1,4 +1,41 @@
1
1
  class E9Crm::OffersController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Offer
3
3
  include E9Rails::Controllers::Orderable
4
+ self.should_paginate_index = false
5
+
6
+ # record attributes templates js
7
+ skip_before_filter :authenticate_user!, :filter_access_filter, :only => :show
8
+
9
+ before_filter :throw_forbidden_unless_offer_cookied, :only => :show
10
+
11
+ has_scope :of_type, :as => :type, :only => :index do |_, scope, value|
12
+ scope.of_type("#{value}_offer".classify)
13
+ end
14
+
15
+ def show
16
+ clear_breadcrumbs
17
+ @show_title = resource.name
18
+ end
19
+
20
+ protected
21
+
22
+ def throw_forbidden_unless_offer_cookied
23
+ cookied_offer_array = Marshal.load(cookies['_e9_offers']) rescue []
24
+
25
+ unless cookied_offer_array.member?(params[:id].to_i)
26
+ permission_denied and return false
27
+ end
28
+ end
29
+
30
+ def find_current_page
31
+ if params[:action] != 'show'
32
+ super
33
+ else
34
+ @current_page ||= Offer.page || super
35
+ end
36
+ end
37
+
38
+ def determine_layout
39
+ request.xhr? ? false : super
40
+ end
4
41
  end
@@ -47,6 +47,14 @@ class E9Crm::ResourcesController < E9Crm::BaseController
47
47
  end
48
48
  end
49
49
 
50
+ def build_params
51
+ params[resource_instance_name] || {}
52
+ end
53
+
54
+ def build_resource
55
+ get_resource_ivar || set_resource_ivar(end_of_association_chain.send(method_for_build, build_params))
56
+ end
57
+
50
58
  def default_ordered_on
51
59
  'created_at'
52
60
  end
@@ -0,0 +1,3 @@
1
+ class E9Crm::VideoOffersController < E9Crm::OfferSubclassController
2
+ defaults :resource_class => VideoOffer
3
+ end
@@ -13,6 +13,38 @@ module E9Crm::DealsHelper
13
13
  select_tag 'contacts_ids', deal_contact_select_options
14
14
  end
15
15
 
16
+ def deal_status_select_options
17
+ @_deal_status_select_options ||= begin
18
+ options = Deal::Status::OPTIONS - %w(lead)
19
+ options.unshift ['All Statuses', nil]
20
+ options_for_select(options)
21
+ end
22
+ end
23
+
24
+ def deal_category_select_options
25
+ @_deal_category_select_options ||= begin
26
+ options = MenuOption.options_for('Deal Category')
27
+ options.unshift ['All Categories', nil]
28
+ options_for_select(options)
29
+ end
30
+ end
31
+
32
+ def deal_owner_select_options
33
+ @_deal_owner_select_options ||= begin
34
+ options = Contact.deal_owners.all.map {|c| [c.name, c.id] }
35
+ options.unshift ['Any Owner', nil]
36
+ options_for_select(options)
37
+ end
38
+ end
39
+
40
+ def deal_offer_select_options
41
+ @_deal_offer_select_options ||= begin
42
+ options = Offer.all.map {|c| [c.name, c.id] }
43
+ options.unshift ['Any/No Offer', nil]
44
+ options_for_select(options)
45
+ end
46
+ end
47
+
16
48
  def deal_date_select_options(ending_month = false)
17
49
  @_first_deal_date ||= Deal.order(:created_at).first.try(:created_at) || Date.today
18
50
 
@@ -0,0 +1,63 @@
1
+ module E9Crm::LeadsHelper
2
+ def custom_offer_form_fields(offer, form)
3
+ @offer.parsed_custom_form_data.each do |field|
4
+ field.symbolize_keys!
5
+
6
+ case field[:type]
7
+ when 'select' then render_custom_offer_select(field, form)
8
+ when 'checkbox' then render_custom_offer_checkbox(field, form)
9
+ when 'radio' then render_custom_offer_radio(field, form)
10
+ when 'textfield' then render_custom_offer_textfield(field, form)
11
+ when 'textarea' then render_custom_offer_textarea(field, form)
12
+ end
13
+ end
14
+ end
15
+
16
+ def render_custom_offer_select(field, form)
17
+ label_html = field[:required] ? form.label(field[:name], nil, :class => :req) : form.label(field[:name])
18
+ select_html = form.select field[:name], options_for_select(field[:options])
19
+ safe_concat <<-HTML
20
+ <div class="field">
21
+ #{label_html}
22
+ #{select_html}
23
+ </div>
24
+ HTML
25
+ end
26
+
27
+ def render_custom_offer_checkbox(field, form)
28
+ label_html = field[:required] ? form.label(field[:name], nil, :class => :req) : form.label(field[:name])
29
+ safe_concat <<-HTML
30
+ <div class="field checkbox">
31
+ #{label_html}
32
+ </div>
33
+ HTML
34
+ end
35
+
36
+ def render_custom_offer_textfield(field, form)
37
+ label_html = field[:required] ? form.label(field[:name], nil, :class => :req) : form.label(field[:name])
38
+
39
+ safe_concat <<-HTML
40
+ <div class="field">
41
+ #{label_html}
42
+ </div>
43
+ HTML
44
+ end
45
+
46
+ def render_custom_offer_textarea(field, form)
47
+ label_html = field[:required] ? form.label(field[:name], nil, :class => :req) : form.label(field[:name])
48
+
49
+ safe_concat <<-HTML
50
+ <div class="field">
51
+ #{label_html}
52
+ </div>
53
+ HTML
54
+ end
55
+
56
+ def render_custom_offer_radio(field, form)
57
+ safe_concat <<-HTML
58
+ <div class="field radio">
59
+
60
+ </div>
61
+ HTML
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ module E9Crm::OffersHelper
2
+ def records_table_field_map_for_offer
3
+ {
4
+ :fields => {
5
+ :name => nil,
6
+ :type => lambda {|r| r.class.model_name.human }
7
+ }
8
+ }
9
+ end
10
+
11
+ def offer_select_options(with_all_option = true)
12
+ options = %w(contact file_download new_content_subscription newsletter_subscription video).map {|t| [t.titleize, t] }
13
+ options.unshift(['All Types', nil]) if with_all_option
14
+ options_for_select(options)
15
+ end
16
+ end
@@ -35,6 +35,7 @@ class Campaign < ActiveRecord::Base
35
35
  scope :active, lambda {|val=true| where(:active => val) }
36
36
  scope :inactive, lambda { active(false) }
37
37
  scope :of_group, lambda {|val| where(:campaign_group_id => val.to_param) }
38
+ scope :typed, lambda { where(arel_table[:type].not_eq('NoCampaign')) }
38
39
 
39
40
  def new_visit_session_count
40
41
  page_views.new_visits.group(:session).count.keys.length
@@ -160,6 +160,8 @@ class Contact < ActiveRecord::Base
160
160
  end
161
161
  }
162
162
 
163
+ scope :deal_owners, lambda { scoped }
164
+
163
165
  def self.available_to_deal(deal)
164
166
  return all unless deal.persisted?
165
167
 
@@ -0,0 +1,2 @@
1
+ class ContactOffer < Offer
2
+ end
data/app/models/deal.rb CHANGED
@@ -4,25 +4,54 @@
4
4
  class Deal < ActiveRecord::Base
5
5
  include E9Rails::ActiveRecord::Initialization
6
6
  include E9Rails::ActiveRecord::Scopes::Times
7
+ include E9Rails::ActiveRecord::InheritableOptions
8
+
9
+ self.options_column = false
7
10
 
8
11
  belongs_to :campaign, :inverse_of => :deals
9
- belongs_to :tracking_cookie, :inverse_of => :deals
10
12
  belongs_to :offer, :inverse_of => :deals
13
+ belongs_to :user
11
14
 
12
15
  belongs_to :owner, :class_name => 'Contact', :foreign_key => :contact_id
13
16
  has_and_belongs_to_many :contacts
14
17
 
15
18
  money_columns :value
16
19
 
17
- validates :name, :presence => true
18
- validates :value, :numericality => true
20
+ validates :value, :numericality => true
21
+
22
+ # non-lead validations (deals in the admin)
23
+ # require a deal name
24
+ validates :name, :presence => { :unless => lambda {|r| r.lead? } }
25
+
26
+ # lead only validations
27
+ # require lead_name and lead_email; gotten from current_user if it exists
28
+ validates :lead_name, :presence => { :if => lambda {|r| r.lead? } }
29
+ validates :lead_email, :presence => { :if => lambda {|r| r.lead? } },
30
+ :email => { :if => lambda {|r| r.lead? }, :allow_blank => true }
31
+
32
+ # If a lead with a user, get the lead_name and lead_email from the user before validation
33
+ before_validation :get_name_and_email_from_user, :only => :create
34
+ before_validation :update_to_pending_status, :only => :update
35
+
36
+ # copy temp options over into custom_info column
37
+ before_create :transform_options_column
19
38
 
39
+ # denormalize campaign code and offer name columns
40
+ before_save :ensure_denormalized_columns
41
+
42
+ # If a lead with no user, find the user by email or create it, then if mailing_lists
43
+ # were passed, assign the user those mailing lists
44
+ after_create :find_or_create_user, :assign_user_mailing_lists
45
+
46
+ # money column definitions for pseudo attributes (added on the reports scope)
20
47
  %w(total_value average_value total_cost average_cost).each do |money_column|
21
48
  class_eval("def #{money_column}; (r = read_attribute(:#{money_column})) && Money.new(r) end")
22
49
  end
23
50
 
24
51
  delegate :name, :to => :owner, :prefix => true, :allow_nil => true
25
52
 
53
+ attr_accessor :mailing_list_ids
54
+
26
55
  scope :reports, lambda {
27
56
  select_sql = <<-SELECT.gsub(/\s+/, ' ')
28
57
  campaigns.id campaign_id,
@@ -122,13 +151,34 @@ class Deal < ActiveRecord::Base
122
151
  column_op(:eq, column, value, reverse)
123
152
  }
124
153
 
125
- scope :leads, lambda {|reverse=true| column_eq(:status, Status::Lead, !reverse) }
126
- scope :pending, lambda {|reverse=true| column_eq(:status, Status::Pending, !reverse) }
127
- scope :won, lambda {|reverse=true| column_eq(:status, Status::Won, !reverse) }
128
- scope :lost, lambda {|reverse=true| column_eq(:status, Status::Lost, !reverse) }
154
+ scope :leads, lambda {|reverse=true| column_eq(:status, Status::Lead, !reverse) }
155
+ scope :pending, lambda {|reverse=true| column_eq(:status, Status::Pending, !reverse) }
156
+ scope :won, lambda {|reverse=true| column_eq(:status, Status::Won, !reverse) }
157
+ scope :lost, lambda {|reverse=true| column_eq(:status, Status::Lost, !reverse) }
158
+
159
+ scope :category, lambda {|category| where(:category => category) }
160
+ scope :offer, lambda {|offer| where(:offer_id => offer.to_param) }
161
+ scope :owner, lambda {|owner| where(:contact_id => owner.to_param) }
162
+ scope :status, lambda {|status| where(:status => status) }
163
+
164
+ def custom_info
165
+ read_attribute(:options)
166
+ end
167
+
168
+ def custom_info=(v)
169
+ write_attribute(:options, v)
170
+ end
129
171
 
130
172
  protected
131
173
 
174
+ def write_options(obj={})
175
+ @custom_options = obj.to_hash
176
+ end
177
+
178
+ def read_options
179
+ @custom_options ||= {}
180
+ end
181
+
132
182
  def method_missing(method_name, *args)
133
183
  if method_name =~ /(.*)\?$/ && Status::OPTIONS.member?($1)
134
184
  self.status == $1
@@ -148,7 +198,7 @@ class Deal < ActiveRecord::Base
148
198
  end
149
199
 
150
200
  def _do_close
151
- self.closed_at = nil
201
+ self.closed_at = Time.now.utc
152
202
  notify_observers :before_close
153
203
  end
154
204
 
@@ -158,6 +208,58 @@ class Deal < ActiveRecord::Base
158
208
  self.status ||= Status::Pending
159
209
  end
160
210
 
211
+ def transform_options_column
212
+ # NOTE this column can't be passed, it is only generated from
213
+ # the custom options pseudo column
214
+ self.custom_info = begin
215
+ options.to_hash.map do |k, v|
216
+ "%s:\n%s\n\n" % [k.to_s.titleize, v]
217
+ end.join
218
+ end
219
+ end
220
+
221
+ def ensure_denormalized_columns
222
+ self.campaign_code ||= campaign.code if campaign.present?
223
+ self.offer_name ||= offer.name if offer.present?
224
+ end
225
+
226
+ def get_name_and_email_from_user
227
+ if lead? && user.present?
228
+ self.lead_email = user.email
229
+ self.lead_name = user.first_name
230
+ end
231
+ end
232
+
233
+ def find_or_create_user
234
+ if lead? && user.blank? && lead_email
235
+ u = User.find_by_email(lead_email) || create_prospect
236
+ update_attribute(:user_id, u.id)
237
+
238
+ u.create_contact_if_missing!
239
+ self.contacts << u.contact
240
+ end
241
+ end
242
+
243
+ def assign_user_mailing_lists
244
+ if @mailing_list_ids
245
+ user.mailing_list_ids |= @mailing_list_ids
246
+ end
247
+ end
248
+
249
+ def update_to_pending_status
250
+ if self.status == Status::Lead
251
+ self.status = Status::Pending
252
+ end
253
+ end
254
+
255
+ def create_prospect
256
+ create_user(
257
+ :email => lead_email,
258
+ :first_name => lead_name,
259
+ :status => User::Status::Prospect
260
+ )
261
+ end
262
+
161
263
  module Status
162
264
  OPTIONS = %w(lead pending won lost)
163
265
  Lead = OPTIONS[0]
@@ -0,0 +1,2 @@
1
+ class FileDownloadOffer < Offer
2
+ end
@@ -0,0 +1,5 @@
1
+ class NewContentSubscriptionOffer < SubscriptionOffer
2
+ def self.mailing_lists
3
+ [::MailingList.new_content_alerts]
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class NewsletterSubscriptionOffer < SubscriptionOffer
2
+ def self.mailing_lists
3
+ [::MailingList.newsletter]
4
+ end
5
+ end
@@ -1,5 +1,9 @@
1
1
  class NoCampaign < Campaign
2
+ #before_create do |record|
3
+ #record.code ||= 'nocode'
4
+ #end
5
+
2
6
  def name
3
- self.class.human_attribute_name(:name)
7
+ self.class.model_name.human
4
8
  end
5
9
  end