e9_crm 0.1.1 → 0.1.4

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 (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