e9_crm 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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