e9_crm 0.1.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/app/controllers/e9_crm/advertising_campaigns_controller.rb +1 -0
  2. data/app/controllers/e9_crm/affiliate_campaigns_controller.rb +1 -0
  3. data/app/controllers/e9_crm/campaign_groups_controller.rb +8 -0
  4. data/app/controllers/e9_crm/campaigns_controller.rb +38 -1
  5. data/app/controllers/e9_crm/companies_controller.rb +1 -0
  6. data/app/controllers/e9_crm/contact_emails_controller.rb +3 -7
  7. data/app/controllers/e9_crm/contacts_controller.rb +6 -4
  8. data/app/controllers/e9_crm/dated_costs_controller.rb +1 -0
  9. data/app/controllers/e9_crm/deals_controller.rb +66 -2
  10. data/app/controllers/e9_crm/email_campaigns_controller.rb +1 -0
  11. data/app/controllers/e9_crm/email_templates.controller.rb +1 -0
  12. data/app/controllers/e9_crm/offers_controller.rb +1 -0
  13. data/app/controllers/e9_crm/page_views_controller.rb +1 -0
  14. data/app/controllers/e9_crm/resources_controller.rb +2 -1
  15. data/app/controllers/e9_crm/sales_campaigns_controller.rb +1 -0
  16. data/app/helpers/e9_crm/campaign_groups_helper.rb +8 -0
  17. data/app/helpers/e9_crm/campaigns_helper.rb +42 -11
  18. data/app/helpers/e9_crm/contacts_helper.rb +1 -1
  19. data/app/helpers/e9_crm/deals_helper.rb +25 -0
  20. data/app/models/advertising_campaign.rb +0 -1
  21. data/app/models/campaign.rb +40 -8
  22. data/app/models/campaign_group.rb +6 -0
  23. data/app/models/contact.rb +22 -0
  24. data/app/models/contact_email.rb +29 -18
  25. data/app/models/deal.rb +93 -14
  26. data/app/models/no_campaign.rb +5 -0
  27. data/app/models/page_view.rb +13 -44
  28. data/app/models/tracking_cookie.rb +0 -6
  29. data/app/observers/deal_observer.rb +3 -0
  30. data/app/views/e9_crm/advertising_campaigns/_form_inner.html.haml +1 -0
  31. data/app/views/e9_crm/affiliate_campaigns/_form_inner.html.haml +10 -0
  32. data/app/views/e9_crm/campaign_groups/_footer.html.haml +0 -0
  33. data/app/views/e9_crm/campaign_groups/_header.html.haml +3 -0
  34. data/app/views/e9_crm/campaigns/_footer.html.haml +0 -0
  35. data/app/views/e9_crm/campaigns/_form_inner.html.haml +20 -4
  36. data/app/views/e9_crm/campaigns/_header.html.haml +16 -0
  37. data/app/views/e9_crm/campaigns/_reports_table.html.haml +31 -0
  38. data/app/views/e9_crm/campaigns/_table.html.haml +31 -0
  39. data/app/views/e9_crm/campaigns/reports.html.haml +13 -0
  40. data/app/views/e9_crm/contact_emails/_form_inner.html.haml +1 -1
  41. data/app/views/e9_crm/contacts/_header.html.haml +5 -4
  42. data/app/views/e9_crm/deals/_reports_table.html.haml +86 -0
  43. data/app/views/e9_crm/deals/reports.html.haml +19 -0
  44. data/app/views/e9_crm/deals/reports.js.erb +1 -0
  45. data/app/views/e9_crm/email_campaigns/_form_inner.html.haml +1 -0
  46. data/app/views/e9_crm/page_views/_table.html.haml +7 -7
  47. data/app/views/e9_crm/resources/_table.html.haml +1 -1
  48. data/app/views/e9_crm/sales_campaigns/_form_inner.html.haml +11 -0
  49. data/config/locales/e9.en.yml +11 -1
  50. data/config/locales/en.yml +16 -5
  51. data/config/routes.rb +15 -12
  52. data/e9_crm.gemspec +1 -1
  53. data/lib/e9_crm/rails_extensions.rb +7 -0
  54. data/lib/e9_crm/tracking_controller.rb +69 -52
  55. data/lib/e9_crm/version.rb +1 -1
  56. data/lib/generators/e9_crm/install_generator.rb +1 -1
  57. data/lib/generators/e9_crm/templates/{create_e9_crm_tables.rb → migration.rb} +6 -7
  58. metadata +20 -6
  59. data/app/controllers/e9_crm/record_attributes_controller.rb +0 -3
  60. data/app/controllers/e9_crm/reports_controller.rb +0 -2
@@ -1,3 +1,4 @@
1
1
  class E9Crm::AdvertisingCampaignsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => AdvertisingCampaign
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -1,3 +1,4 @@
1
1
  class E9Crm::AffiliateCampaignsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => AffiliateCampaign
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -1,3 +1,11 @@
1
1
  class E9Crm::CampaignGroupsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => CampaignGroup
3
+ include E9Rails::Controllers::Orderable
4
+
5
+ protected
6
+
7
+ # no pagination
8
+ def collection
9
+ get_collection_ivar || set_collection_ivar(end_of_association_chain.all)
10
+ end
3
11
  end
@@ -1,4 +1,41 @@
1
1
  class E9Crm::CampaignsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Campaign
3
- has_scope :of_type, :as => :t, :only => :index
3
+ include E9Rails::Controllers::Orderable
4
+
5
+ filter_access_to :reports, :require => :read, :context => :admin
6
+
7
+ has_scope :of_group, :as => :group, :only => :index
8
+
9
+ has_scope :active, :only => :index do |_, scope, value|
10
+ scope.active(E9.true_value?(value))
11
+ end
12
+
13
+ has_scope :of_type, :as => :type, :only => :index do |_, scope, value|
14
+ scope.of_type("#{value}_campaign".classify)
15
+ end
16
+
17
+ def reports
18
+ index!
19
+ end
20
+
21
+ protected
22
+
23
+ def set_reports_index_title
24
+ @index_title = I18n.t(:reports_title, :scope => 'e9.e9_crm.campaigns')
25
+ end
26
+
27
+ def collection_scope
28
+ scope = end_of_association_chain
29
+ if params[:action] == 'reports'
30
+ scope = scope.select(
31
+ 'campaigns.*, count(deals.id) won_deals_count, count(deals_campaigns.id) deals_count'
32
+ ).joins([:deals, :won_deals])
33
+ end
34
+ scope
35
+ end
36
+
37
+ # no pagination
38
+ def collection
39
+ get_collection_ivar || set_collection_ivar(collection_scope.all)
40
+ end
4
41
  end
@@ -1,3 +1,4 @@
1
1
  class E9Crm::CompaniesController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Company
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -17,21 +17,17 @@ class E9Crm::ContactEmailsController < E9Crm::ResourcesController
17
17
  object = if params[resource_instance_name]
18
18
  ContactEmail.new(params[resource_instance_name] || {})
19
19
  else
20
- ContactEmail.new_from_template(template, :from_email => current_user.email, :user_ids => params[:uids])
20
+ ContactEmail.new_from_template(template, :from_email => current_user.email, :contact_ids => params[:uids])
21
21
  end
22
22
 
23
- # we set the user_ids.blank? error right away signifiying a problem,
24
- # as the record won't be valid if the user_ids weren't passed in params
25
- if object.user_ids.blank?
26
- object.errors.add(:user_ids, :blank)
27
- end
23
+ object.valid?
28
24
 
29
25
  set_resource_ivar(object)
30
26
  end
31
27
  end
32
28
 
33
29
  # throw record_not_found if there's no template. #new requires email_template_id
34
- # be passed in params (and also user_ids)
30
+ # be passed in params (and also contact_ids)
35
31
  def template
36
32
  @_template ||= EmailTemplate.find(params[:etid])
37
33
  end
@@ -1,12 +1,13 @@
1
1
  class E9Crm::ContactsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Contact
3
3
 
4
+ include E9Rails::Controllers::Orderable
4
5
  include E9Tags::Controller
5
6
 
6
7
  respond_to :js, :html
7
8
 
8
9
  before_filter :determine_title, :only => :index
9
- before_filter :load_user_ids, :only => :index
10
+ before_filter :load_contact_ids, :only => :index
10
11
 
11
12
  has_scope :search, :by_title, :by_company, :only => :index
12
13
  has_scope :tagged, :only => :index, :type => :array
@@ -18,9 +19,10 @@ class E9Crm::ContactsController < E9Crm::ResourcesController
18
19
 
19
20
  protected
20
21
 
21
- def load_user_ids
22
- @user_ids ||= begin
23
- (User.primary.joins(:contact) & end_of_association_chain.scoped).all.map(&:id)
22
+ def load_contact_ids
23
+ @contact_ids ||= begin
24
+ contact_id_sql = end_of_association_chain.scoped.select('contacts.id').to_sql
25
+ Contact.connection.send(:select_values, contact_id_sql, 'Contact ID Load')
24
26
  end
25
27
  end
26
28
 
@@ -1,4 +1,5 @@
1
1
  class E9Crm::DatedCostsController < E9Crm::ResourcesController
2
2
  belongs_to :advertising_campaign
3
3
  defaults :resource_class => DatedCost
4
+ include E9Rails::Controllers::Orderable
4
5
  end
@@ -1,19 +1,83 @@
1
1
  class E9Crm::DealsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Deal
3
+ include E9Rails::Controllers::Orderable
4
+
5
+ # for campaign select options
6
+ helper :"e9_crm/campaigns"
7
+
8
+ filter_access_to :leads, :reports, :require => :read, :context => :admin
3
9
 
4
- filter_access_to :leads, :require => :read, :context => :admin
5
10
  prepend_before_filter :set_leads_index_title, :only => :leads
11
+ prepend_before_filter :set_reports_index_title, :only => :reports
12
+
13
+ ##
14
+ # Index/Reports Scopes
15
+ #
6
16
 
7
17
  has_scope :leads, :only => :leads, :default => true
8
- has_scope :leads, :except => :leads, :default => false
18
+ has_scope :leads, :except => [:leads, :reports], :default => false
19
+
20
+
21
+ ##
22
+ # Reports scopes
23
+ #
24
+
25
+ has_scope :reports, :only => :reports, :type => :boolean, :default => true
26
+
27
+ has_scope :group, :only => :reports do |c, scope, value|
28
+ scope & Campaign.of_group(value)
29
+ end
30
+
31
+ has_scope :type, :only => :reports do |_, scope, value|
32
+ scope & Campaign.of_type("#{value}_campaign".classify)
33
+ end
34
+
35
+ has_scope :until_time, :as => :until, :unless => 'params[:from].present?'
36
+
37
+ has_scope :from_time, :as => :from do |controller, scope, value|
38
+ if controller.params[:until]
39
+ scope.for_time_range(value, controller.params[:until])
40
+ else
41
+ scope.from_time(value)
42
+ end
43
+ end
44
+
45
+
46
+ ##
47
+ # Actions
48
+ #
9
49
 
10
50
  def leads
11
51
  index!
12
52
  end
13
53
 
54
+ def reports
55
+ index!
56
+ end
57
+
14
58
  protected
15
59
 
60
+ def collection
61
+ get_collection_ivar || begin
62
+ set_collection_ivar(
63
+ if params[:action] == 'reports'
64
+ end_of_association_chain.all
65
+ else
66
+ end_of_association_chain.paginate(pagination_parameters)
67
+ end
68
+ )
69
+ end
70
+ end
71
+
16
72
  def set_leads_index_title
17
73
  @index_title = I18n.t(:index_title, :scope => 'e9.e9_crm.leads')
18
74
  end
75
+
76
+ def set_reports_index_title
77
+ @index_title = I18n.t(:index_title, :scope => 'e9.e9_crm.reports')
78
+ end
79
+
80
+ def ordered_if
81
+ %w(index reports).member? params[:action]
82
+ end
19
83
  end
@@ -1,3 +1,4 @@
1
1
  class E9Crm::EmailCampaignsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => EmailCampaign
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -1,3 +1,4 @@
1
1
  class E9Crm::EmailTemplatesController < E9Crm::ResourcesController
2
2
  defaults
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -1,3 +1,4 @@
1
1
  class E9Crm::OffersController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Offer
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -1,6 +1,7 @@
1
1
  class E9Crm::PageViewsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => PageView
3
3
  belongs_to :campaign, :contact, :polymorphic => true
4
+ include E9Rails::Controllers::Orderable
4
5
 
5
6
  # NOTE association chain is prepended to ensure parent is loaded so other
6
7
  # before filters can use collection_path, etc. Is there a better solution
@@ -1,10 +1,11 @@
1
1
  class E9Crm::ResourcesController < E9Crm::BaseController
2
- include E9Rails::Controllers::Orderable
3
2
  include E9Rails::Helpers::ResourceErrorMessages
4
3
  include E9Rails::Helpers::Pagination
5
4
 
6
5
  inherit_resources
7
6
 
7
+ respond_to :js
8
+
8
9
  add_resource_breadcrumbs
9
10
 
10
11
  def self.defaults(hash = {})
@@ -1,3 +1,4 @@
1
1
  class E9Crm::SalesCampaignsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => SalesCampaign
3
+ include E9Rails::Controllers::Orderable
3
4
  end
@@ -0,0 +1,8 @@
1
+ module E9Crm::CampaignGroupsHelper
2
+ def records_table_field_map_for_campaign_group
3
+ {
4
+ :fields => { :name => nil },
5
+ :links => proc {|r| [link_to_edit_resource(r), link_to_destroy_resource(r)] }
6
+ }
7
+ end
8
+ end
@@ -1,14 +1,45 @@
1
1
  module E9Crm::CampaignsHelper
2
- def records_table_map_for_campaign
3
- {
4
- :fields => {
5
- :type => nil,
6
- :name => nil,
7
- :code => nil,
8
- :affiliate_fee => proc {|r| v = r.affiliate_fee; Money === v ? v : 'n/a' },
9
- :sales_fee => nil,
10
- :status => proc {|r| resource_class.human_attribute_name(Campaign::Status::VALUES[r.status]) }
11
- }
12
- }
2
+ def display_campaign_fee(val)
3
+ Money === val && val || 'n/a'
4
+ end
5
+
6
+ def display_campaign_code(val)
7
+ val || 'n/a'
8
+ end
9
+
10
+ def display_campaign_type(val)
11
+ val[/(.*)Campaign/, 1]
12
+ end
13
+
14
+ def campaign_type_select_options(with_all_option = true)
15
+ options = %w( advertising affiliate email sales ).map {|t| [t.titleize, t] }
16
+ options.unshift(['All Types', nil]) if with_all_option
17
+ options_for_select(options)
18
+ end
19
+
20
+ def campaign_group_select_options
21
+ options = CampaignGroup.select('name, id').all.map {|c| [c.name, c.id] }
22
+ options.unshift(['All Groups', nil])
23
+ options_for_select(options)
24
+ end
25
+
26
+ def campaign_date_select_options
27
+ ''
28
+ end
29
+
30
+ def campaign_active_select_options
31
+ options = [
32
+ ['All Statuses', nil],
33
+ ['Active', true],
34
+ ['Inactive', false]
35
+ ]
36
+ options_for_select(options)
37
+ end
38
+
39
+ ##
40
+ # Accommodate for "NoCampaign" campaign in link
41
+ #
42
+ def link_to_edit_campaign(record)
43
+ link_to_edit_resource(record) unless record.is_a?(NoCampaign)
13
44
  end
14
45
  end
@@ -15,7 +15,7 @@ module E9Crm::ContactsHelper
15
15
  def records_table_field_map_for_contact
16
16
  {
17
17
  :fields => {
18
- :avatar => proc {|r| },
18
+ :avatar => proc {|r| "<img src=\"#{r.avatar_url}\" alt=\"Avatar for #{r.name}\" />".html_safe },
19
19
  :details => proc {|r| render('details', :record => r) }
20
20
  },
21
21
 
@@ -12,4 +12,29 @@ module E9Crm::DealsHelper
12
12
  }
13
13
  }
14
14
  end
15
+
16
+ def deal_date_select_options(ending_month = false)
17
+ @_first_deal_date ||= Deal.order(:created_at).first.try(:created_at) || Date.today
18
+
19
+ date, cdate = @_first_deal_date, Date.today
20
+
21
+ options = []
22
+
23
+ if ending_month
24
+ prefix = 'Until'
25
+ label = prefix + ' Now'
26
+ else
27
+ prefix = 'From'
28
+ label = prefix + ' Inception'
29
+ end
30
+
31
+ begin
32
+ options << [date.strftime("#{prefix} %B %Y"), date.strftime('%Y/%m')]
33
+ date += 1.month
34
+ end while date.year <= cdate.year && date.month <= cdate.month
35
+
36
+ options.unshift([label, nil])
37
+
38
+ options_for_select(options)
39
+ end
15
40
  end
@@ -4,7 +4,6 @@
4
4
  # DatedCost records.
5
5
  #
6
6
  class AdvertisingCampaign < Campaign
7
- has_many :dated_costs, :as => :costable
8
7
 
9
8
  ##
10
9
  # The sum cost of this campaign
@@ -7,18 +7,50 @@ class Campaign < ActiveRecord::Base
7
7
  include E9Rails::ActiveRecord::STI
8
8
 
9
9
  belongs_to :campaign_group
10
- has_many :deals, :inverse_of => :campaign
11
- has_many :page_views, :inverse_of => :campaign
10
+
11
+ has_many :deals, :inverse_of => :campaign, :dependent => :nullify
12
+ has_many :won_deals, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Won]
13
+ has_many :lost_deals, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Lost]
14
+ has_many :pending_deals, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Pending]
15
+ has_many :leads, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Lead]
16
+
17
+ has_many :page_views, :inverse_of => :campaign, :dependent => :nullify
18
+
19
+ # only advertising campaigns use this association
20
+ has_many :dated_costs, :as => :costable
12
21
 
13
22
  # NOTE tracking cookie code changes with new visits
14
- has_many :tracking_cookies, :foreign_key => :code, :primary_key => :code, :class_name => 'TrackingCookie'
23
+ has_many :tracking_cookies, :foreign_key => :code, :primary_key => :code, :class_name => 'TrackingCookie'
15
24
 
16
- validates :code, :presence => true,
17
- :length => { :maximum => 32 },
18
- :uniqueness => { :ignore_case => true }
25
+ def self.default
26
+ NoCampaign.first || NoCampaign.create
27
+ end
19
28
 
20
- scope :active, lambda { where(:active => true) }
21
- scope :inactive, lambda { where(:active => false) }
29
+ validates :code, :presence => { :unless => lambda {|r| r.is_a?(NoCampaign) } },
30
+ :length => { :maximum => 32 },
31
+ :uniqueness => { :ignore_case => true, :allow_blank => true }
32
+ validates :affiliate_fee, :numericality => true
33
+ validates :sales_fee, :numericality => true
34
+
35
+ scope :active, lambda {|val=true| where(:active => val) }
36
+ scope :inactive, lambda { active(false) }
37
+ scope :of_group, lambda {|val| where(:campaign_group_id => val.to_param) }
38
+
39
+ def new_visit_session_count
40
+ page_views.new_visits.group(:session).count.keys.length
41
+ end
42
+
43
+ def new_visit_page_view_count
44
+ page_views.new_visits.group(:session).count.values.sum
45
+ end
46
+
47
+ def repeat_visit_session_count
48
+ page_views.repeat_visits.group(:session).count.keys.length
49
+ end
50
+
51
+ def repeat_visit_session_count
52
+ page_views.repeat_visits.group(:session).count.values.sum
53
+ end
22
54
 
23
55
  ##
24
56
  # The sum cost of this campaign