e9_crm 0.1.1

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 (120) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +105 -0
  4. data/README.md +31 -0
  5. data/Rakefile +2 -0
  6. data/app/controllers/e9_crm/advertising_campaigns_controller.rb +3 -0
  7. data/app/controllers/e9_crm/affiliate_campaigns_controller.rb +3 -0
  8. data/app/controllers/e9_crm/base_controller.rb +5 -0
  9. data/app/controllers/e9_crm/campaign_groups_controller.rb +3 -0
  10. data/app/controllers/e9_crm/campaigns_controller.rb +4 -0
  11. data/app/controllers/e9_crm/companies_controller.rb +3 -0
  12. data/app/controllers/e9_crm/contact_emails_controller.rb +38 -0
  13. data/app/controllers/e9_crm/contact_merges_controller.rb +26 -0
  14. data/app/controllers/e9_crm/contacts_controller.rb +46 -0
  15. data/app/controllers/e9_crm/dated_costs_controller.rb +4 -0
  16. data/app/controllers/e9_crm/deals_controller.rb +19 -0
  17. data/app/controllers/e9_crm/email_campaigns_controller.rb +3 -0
  18. data/app/controllers/e9_crm/email_templates.controller.rb +3 -0
  19. data/app/controllers/e9_crm/menu_options_controller.rb +26 -0
  20. data/app/controllers/e9_crm/offers_controller.rb +3 -0
  21. data/app/controllers/e9_crm/page_views_controller.rb +20 -0
  22. data/app/controllers/e9_crm/record_attributes_controller.rb +3 -0
  23. data/app/controllers/e9_crm/reports_controller.rb +2 -0
  24. data/app/controllers/e9_crm/resources_controller.rb +43 -0
  25. data/app/controllers/e9_crm/sales_campaigns_controller.rb +3 -0
  26. data/app/helpers/e9_crm/base_helper.rb +77 -0
  27. data/app/helpers/e9_crm/campaigns_helper.rb +14 -0
  28. data/app/helpers/e9_crm/contact_merges_helper.rb +17 -0
  29. data/app/helpers/e9_crm/contacts_helper.rb +27 -0
  30. data/app/helpers/e9_crm/deals_helper.rb +15 -0
  31. data/app/helpers/e9_crm/menu_options_helper.rb +11 -0
  32. data/app/helpers/e9_crm/page_views_helper.rb +2 -0
  33. data/app/models/address_attribute.rb +4 -0
  34. data/app/models/advertising_campaign.rb +15 -0
  35. data/app/models/affiliate.rb +4 -0
  36. data/app/models/affiliate_campaign.rb +15 -0
  37. data/app/models/campaign.rb +34 -0
  38. data/app/models/campaign_group.rb +5 -0
  39. data/app/models/company.rb +4 -0
  40. data/app/models/contact.rb +258 -0
  41. data/app/models/contact_email.rb +49 -0
  42. data/app/models/dated_cost.rb +9 -0
  43. data/app/models/deal.rb +83 -0
  44. data/app/models/email_campaign.rb +13 -0
  45. data/app/models/email_template.rb +7 -0
  46. data/app/models/instant_messaging_handle_attribute.rb +4 -0
  47. data/app/models/menu_option.rb +33 -0
  48. data/app/models/offer.rb +50 -0
  49. data/app/models/page_view.rb +80 -0
  50. data/app/models/phone_number_attribute.rb +4 -0
  51. data/app/models/record_attribute.rb +41 -0
  52. data/app/models/sales_campaign.rb +15 -0
  53. data/app/models/sales_person.rb +4 -0
  54. data/app/models/tracking_cookie.rb +61 -0
  55. data/app/models/website_attribute.rb +4 -0
  56. data/app/observers/deal_observer.rb +11 -0
  57. data/app/uploaders/file_uploader.rb +34 -0
  58. data/app/views/e9_crm/campaigns/_form_inner.html.haml +5 -0
  59. data/app/views/e9_crm/contact_emails/_form.html.haml +7 -0
  60. data/app/views/e9_crm/contact_emails/_form_inner.html.haml +11 -0
  61. data/app/views/e9_crm/contact_emails/destroy.js.erb +3 -0
  62. data/app/views/e9_crm/contact_emails/send_email.js.erb +1 -0
  63. data/app/views/e9_crm/contact_merges/_field.html.haml +10 -0
  64. data/app/views/e9_crm/contact_merges/_form.html.haml +10 -0
  65. data/app/views/e9_crm/contact_merges/new.html.haml +2 -0
  66. data/app/views/e9_crm/contacts/_details.html.haml +22 -0
  67. data/app/views/e9_crm/contacts/_form_inner.html.haml +51 -0
  68. data/app/views/e9_crm/contacts/_header.html.haml +19 -0
  69. data/app/views/e9_crm/contacts/_tag_table.html.haml +15 -0
  70. data/app/views/e9_crm/contacts/index.html.haml +13 -0
  71. data/app/views/e9_crm/contacts/index.js.erb +5 -0
  72. data/app/views/e9_crm/contacts/merge.html.haml +1 -0
  73. data/app/views/e9_crm/contacts/templates.js.erb +1 -0
  74. data/app/views/e9_crm/deals/_form_inner.html.haml +5 -0
  75. data/app/views/e9_crm/deals/_header.html.haml +0 -0
  76. data/app/views/e9_crm/deals/leads.html.haml +13 -0
  77. data/app/views/e9_crm/email_templates/_form_inner.html.haml +9 -0
  78. data/app/views/e9_crm/menu_options/_form_inner.html.haml +6 -0
  79. data/app/views/e9_crm/menu_options/_header.html.haml +8 -0
  80. data/app/views/e9_crm/offers/_form.html.haml +7 -0
  81. data/app/views/e9_crm/offers/_form_inner.html.haml +42 -0
  82. data/app/views/e9_crm/offers/_form_inner.html.haml.bak +43 -0
  83. data/app/views/e9_crm/page_views/_table.html.haml +25 -0
  84. data/app/views/e9_crm/record_attributes/_address_attribute.html.haml +5 -0
  85. data/app/views/e9_crm/record_attributes/_instant_messaging_handle_attribute.html.haml +5 -0
  86. data/app/views/e9_crm/record_attributes/_phone_number_attribute.html.haml +5 -0
  87. data/app/views/e9_crm/record_attributes/_record_attribute.html.haml +10 -0
  88. data/app/views/e9_crm/record_attributes/_templates.js.erb +12 -0
  89. data/app/views/e9_crm/record_attributes/_user.html.haml +23 -0
  90. data/app/views/e9_crm/record_attributes/_website_attribute.html.haml +5 -0
  91. data/app/views/e9_crm/resources/_footer.html.haml +1 -0
  92. data/app/views/e9_crm/resources/_form.html.haml +7 -0
  93. data/app/views/e9_crm/resources/_form_inner.html.haml +5 -0
  94. data/app/views/e9_crm/resources/_header.html.haml +0 -0
  95. data/app/views/e9_crm/resources/_table.html.haml +21 -0
  96. data/app/views/e9_crm/resources/create.js.erb +6 -0
  97. data/app/views/e9_crm/resources/destroy.js.erb +3 -0
  98. data/app/views/e9_crm/resources/edit.html.haml +2 -0
  99. data/app/views/e9_crm/resources/index.html.haml +13 -0
  100. data/app/views/e9_crm/resources/index.js.erb +1 -0
  101. data/app/views/e9_crm/resources/new.html.haml +2 -0
  102. data/app/views/e9_crm/resources/show.html.haml +2 -0
  103. data/app/views/e9_crm/resources/update.js.erb +5 -0
  104. data/config/initializers/inflections.rb +3 -0
  105. data/config/locales/e9.en.yml +28 -0
  106. data/config/locales/en.yml +63 -0
  107. data/config/routes.rb +61 -0
  108. data/e9_crm.gemspec +29 -0
  109. data/lib/e9_crm/model.rb +63 -0
  110. data/lib/e9_crm/rails_extensions.rb +98 -0
  111. data/lib/e9_crm/tracking_controller.rb +78 -0
  112. data/lib/e9_crm/version.rb +3 -0
  113. data/lib/e9_crm.rb +59 -0
  114. data/lib/generators/e9_crm/install_generator.rb +32 -0
  115. data/lib/generators/e9_crm/templates/create_e9_crm_tables.rb +107 -0
  116. data/lib/generators/e9_crm/templates/initializer.rb +4 -0
  117. data/lib/generators/e9_crm/templates/javascript.js +187 -0
  118. data/test/functional/e9_crm/campaign_types_controller_test.rb +49 -0
  119. data/test/functional/home_controller_test.rb +8 -0
  120. metadata +283 -0
@@ -0,0 +1,2 @@
1
+ = title e9_t(:edit_title)
2
+ = render 'form'
@@ -0,0 +1,13 @@
1
+ = title (@index_title || e9_t(:index_title))
2
+
3
+ = render 'header'
4
+
5
+ - if sortable_controller?
6
+ = form_tag polymorphic_path([:update_order, resource_class]) do
7
+ %div#records_table
8
+ = render 'table', :resources => collection
9
+ - else
10
+ %div#records_table
11
+ = render 'table', :resources => collection
12
+
13
+ = render 'footer'
@@ -0,0 +1 @@
1
+ $("table.records").replaceWith("<%= escape_javascript(render('table', :collection => collection)) %>");
@@ -0,0 +1,2 @@
1
+ = title e9_t(:new_title)
2
+ = render 'form'
@@ -0,0 +1,2 @@
1
+ = title e9_t(:show_title)
2
+ = render 'form'
@@ -0,0 +1,5 @@
1
+ <% if resource.errors.any? %>
2
+ $('form .errors').html("<%= escape_javascript(resource_error_messages!) %>");
3
+ <% else %>
4
+ window.location.reload(true);
5
+ <% end %>
@@ -0,0 +1,3 @@
1
+ ActiveSupport::Inflector.inflections do |inflect|
2
+ inflect.singular /^(\w*cookie)s$/i, '\1'
3
+ end
@@ -0,0 +1,28 @@
1
+ en:
2
+ e9:
3
+ e9_crm:
4
+ no_records_text: "No records were found."
5
+ index_title: "%{models}"
6
+ edit_title: "Edit %{model}"
7
+ new_title: "New %{model}"
8
+ actions: Actions
9
+
10
+ contacts:
11
+ index_title_with_search: "%{models} matching \"%{search}\""
12
+ index_title_with_tags: "%{models} tagged \"%{tagged}\""
13
+ index_title_with_search_and_tags: "%{models} tagged \"%{tagged}\", matching \"%{search}\""
14
+ form_legend_contact: Contact Information
15
+ form_legend_standard: Personal Information
16
+ form_legend_user: Login Accounts
17
+ send_email_template: Send Email
18
+ send_email_newsletter: Send Newsletter
19
+ contact_newsletter_confirmation: "The system will send the newsletter to all of the selected contacts (%{count}) if they are subscribed to receieve email. Are you sure you want to proceed?"
20
+ no_contacts_notification: "Either no contacts are selected, or none of those selected have configured primary emails."
21
+ contact_merges:
22
+ new_title: Merge Contacts
23
+ no_value: (No Value)
24
+ contact_emails:
25
+ prepare_title: Edit Your Template
26
+ send_email: Send Email
27
+ leads:
28
+ index_title: Leads
@@ -0,0 +1,63 @@
1
+ en:
2
+ clear: Clear
3
+ go: Go
4
+ search: Search
5
+ view: View
6
+
7
+ e9_crm:
8
+ add_record_attribute: Add
9
+ destroy_record_attribute: Remove
10
+
11
+ activerecord:
12
+ links:
13
+ new: "New %{model}"
14
+ edit: "Edit %{model}"
15
+ destroy: "Delete %{model}"
16
+ show: "View %{model}"
17
+
18
+ errors:
19
+ messages:
20
+ merge_required: 'Merge Required! <a href="%{merge_path}">MERGE ME</a>.'
21
+
22
+ models:
23
+ contact:
24
+ attributes:
25
+ users:
26
+ taken: "Two or more of the login accounts entered share a duplicate email."
27
+ invalid: "One or more of the emails you entered is invalid."
28
+ contact_email:
29
+ attributes:
30
+ user_ids:
31
+ blank: "You cannot send an email with no recipients!"
32
+ deal:
33
+ attributes:
34
+ status:
35
+ illegal_conversion: A Deal with "lead" status can only be converted to "pending"
36
+ illegal_reversion: A Deal that has been marked "won" or "lost" cannot be reverted to a "lead"
37
+ invalid: "Status must be one of (%{options})"
38
+
39
+ models:
40
+ sales_campaign: Sales Campaign
41
+ email_campaign: Email Campaign
42
+ email_template: Email Template
43
+ affiliate_campaign: Affiliate Campaign
44
+ advertising_campaign: Advertising Campaign
45
+ campaign: Campaign
46
+ campaign_type: Campaign Type
47
+ company: Company
48
+ contact: Contact
49
+ deal: Deal
50
+ page_view: Page View
51
+ tracking_cookie: Tracking Cookie
52
+
53
+ attributes:
54
+ campaign:
55
+ name: Name
56
+ offer:
57
+ alert_email_instructions: (Enter an email if you want to be notified of conversions)
58
+ submit_button_text: Submit Button Text
59
+ success_alert_text: Success Alert Text
60
+ download_link_text: Download Link Text
61
+ conversion_alert_email: Send Alert Email To
62
+ success_page_text: Success Page Text
63
+ custom_form_html: Custom Form Html
data/config/routes.rb ADDED
@@ -0,0 +1,61 @@
1
+ Rails.application.routes.draw do
2
+ crm_path = 'admin/crm'
3
+
4
+ scope :path => crm_path, :module => :e9_crm do
5
+ resources :companies, :except => :show
6
+ resources :contacts, :except => :show do
7
+ resources :page_views, :path => 'activity', :only => :index
8
+ collection { get :templates }
9
+ end
10
+ resources :deals, :except => :show
11
+ resources :offers, :except => :show
12
+
13
+ # contact_emails are generated by email templates, and end up in the sent emails list
14
+ resources :contact_emails, :except => [:index, :show]
15
+ resources :email_templates, :except => :show
16
+
17
+ resources :menu_options, :except => [:show] do
18
+ collection { post :update_order }
19
+ end
20
+
21
+ # campaigns_controller only handles index, the individual controllers all support
22
+ # specific index views and manage create/edit/destroy
23
+ resources :campaigns, :only => :index do
24
+ resources :page_views, :path => 'activity', :only => :index
25
+ end
26
+ scope :path => :campaigns do
27
+ get '/activity', :to => redirect("/#{crm_path}/campaigns/all/activity")
28
+
29
+ resources :campaign_groups, :path => 'groups', :except => [:show]
30
+ resources :sales_campaigns, :path => 'sales', :except => [:show]
31
+ resources :advertising_campaigns, :path => 'advertising', :except => [:show] do
32
+ resources :dated_costs, :path => 'costs'
33
+ end
34
+ resources :affiliate_campaigns, :path => 'affiliate', :except => [:show]
35
+ resources :email_campaigns, :path => 'email', :except => [:show]
36
+ end
37
+
38
+ # leads are simply a scoped view of offers (only index)
39
+ get :leads, :as => :leads, :to => 'deals#leads'
40
+ get :marketing_report, :to => 'campaigns#reports', :only => :index
41
+
42
+ get '/merge_contacts/:contact_a_id/and/:contact_b_id', :as => :new_contact_merge, :to => 'contact_merges#new'
43
+ post '/merge_contacts', :as => :contact_merges, :to => 'contact_merges#create'
44
+
45
+ # redirect shows to edits
46
+ %w(
47
+ campaigns/advertising
48
+ campaigns/affiliate
49
+ campaigns/email
50
+ campaigns/sales
51
+ campaigns/groups
52
+ contacts
53
+ deals
54
+ email_templates
55
+ menu_options
56
+ offers
57
+ ).each do |path|
58
+ get "/#{path}/:id", :to => redirect("/#{crm_path}/#{path}/%{id}/edit"), :constraints => { :id => /\d+/ }
59
+ end
60
+ end
61
+ end
data/e9_crm.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "e9_crm/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "e9_crm"
7
+ s.version = E9Crm::VERSION.dup
8
+ s.platform = Gem::Platform::RUBY
9
+ s.email = "travis@e9digital.com"
10
+ s.homepage = "http://www.e9digital.com"
11
+ s.summary = "CRM engine plugin for the e9 CMS"
12
+ s.description = File.open('README.md').read rescue nil
13
+ s.authors = ['Travis Cox']
14
+
15
+ s.rubyforge_project = "e9_crm"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("rails", "~> 3.0.0")
23
+ s.add_dependency("inherited_resources", "~> 1.1.2")
24
+ s.add_dependency("has_scope")
25
+ s.add_dependency("money")
26
+ s.add_dependency("e9_rails", "~> 0.0.11")
27
+ s.add_dependency("e9_tags", "~> 0.0.11")
28
+ s.add_dependency("will_paginate")
29
+ end
@@ -0,0 +1,63 @@
1
+ module E9Crm
2
+ ##
3
+ # The User model associated with a Contact. The idea is that a Contact can be
4
+ # associated to many different logins/users (e.g. bob@home.com, bob@work.com).
5
+ #
6
+ # In the default setup the included model is treated as simply an email, and given a
7
+ # "type" such as Personal, Work, etc.
8
+ #
9
+ # One and only one of these models should be considered "primary". The designation of
10
+ # which is handled largely by the Contact class.
11
+ #
12
+ module Model
13
+ extend ActiveSupport::Concern
14
+ include E9Rails::ActiveRecord::InheritableOptions
15
+
16
+ included do
17
+ belongs_to :contact, :inverse_of => :users
18
+
19
+ has_many :tracking_cookies, :inverse_of => :user
20
+ has_many :page_views, :through => :tracking_cookies
21
+
22
+ self.options_parameters = [:type, :primary]
23
+
24
+ scope :primary, lambda { where(arel_table[:options].matches("%primary: true%")) }
25
+
26
+ before_create :create_contact_if_missing
27
+ after_destroy :cleanup_contact
28
+ end
29
+
30
+ ##
31
+ # Is this the Contact's primary model?
32
+ #
33
+ def primary?
34
+ !!options.primary
35
+ end
36
+
37
+ protected
38
+
39
+ def create_contact_parameters
40
+ { :first_name => self.first_name, :last_name => self.last_name }
41
+ end
42
+
43
+ def create_contact_if_missing
44
+ if contact.blank?
45
+ # when creating a contact we should assume we're primary
46
+ self.options.primary = true
47
+ create_contact(create_contact_parameters)
48
+ end
49
+ end
50
+
51
+ def cleanup_contact
52
+ # NOTE this is called after_delete, reload the contact to clear the deleted
53
+ # user from the contact's #users
54
+ contact.reload.users.reset_primary! if self.primary? && contact.present?
55
+ end
56
+
57
+ module ClassMethods
58
+ def email_types
59
+ MenuOption.fetch_values('Email')
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,98 @@
1
+ require 'active_record/base'
2
+ require 'action_view/base'
3
+
4
+ class ActiveRecord::Base
5
+ #
6
+ # Basic conversion for "money" columns using the Money class and Rails composed_of
7
+ #
8
+ def self.money_columns(*column_names)
9
+ column_names.each do |column_name|
10
+ class_eval <<-EVAL
11
+ composed_of :#{column_name},
12
+ :class_name => 'Money',
13
+ :mapping => %w(#{column_name} cents),
14
+ :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : Money.empty }
15
+ EVAL
16
+ end
17
+ end
18
+ end
19
+
20
+ module E9Crm
21
+ #
22
+ # Simple string subclass that lets you query the string against
23
+ # a "label" it was initialized with. e.g.
24
+ #
25
+ # LabeledString.new(:omfg, 'whatever').omfg? #=> true
26
+ #
27
+ class LabeledString < String
28
+ attr_reader :label
29
+
30
+ def initialize(label, *args)
31
+ @label = label if label.is_a?(Symbol)
32
+ super(*args)
33
+ end
34
+
35
+ def method_missing(method_name, *args)
36
+ if @label && method_name.to_s[-1,1] == '?'
37
+ method_name.to_s[0..-2] == @label.to_s
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module ActiveModel
46
+ class Errors
47
+ alias :default_add :add
48
+
49
+ # Monkeypatch add to replace the error message with a LabeledString, which
50
+ # simply changes method missing to respond to methods ending with '?' by
51
+ # checking to see if their label matches the method.
52
+ #
53
+ # This makes it easy to check an error's message for what error symbol it
54
+ # was generated for, e.g. :invalid, by calling #invalid? on it.
55
+ #
56
+ # Errors#add accepts a Proc or a Symbol for message (or defaults to :invalid)
57
+ # so this is hack isn't always useful, but for my purposes I want to know when
58
+ # a :taken error was added to a User#email, and this is good enough.
59
+ #
60
+ def add(attribute, message = nil, options = {})
61
+ default_add(attribute, message, options)
62
+
63
+ self[attribute] << E9Crm::LabeledString.new(message, self[attribute].pop)
64
+ end
65
+ end
66
+ end
67
+
68
+ module E9Crm
69
+ #
70
+ # Fairly straightforward hack to allow e9_crm views to inherit from resources.
71
+ # If a missing template error is raised by a view looking for a template prefixed with
72
+ # "e9_crm", redirect the lookup to "e9_crm/resources"
73
+ #
74
+ # This would be worth looking into further but inherited templates are a feature coming
75
+ # up in Rails anyway.
76
+ #
77
+ module ActionView
78
+ extend ActiveSupport::Concern
79
+
80
+ included do
81
+ def self.process_view_paths(value)
82
+ value.is_a?(::E9Crm::ActionView::PathSet) ?
83
+ value.dup : ::E9Crm::ActionView::PathSet.new(Array.wrap(value))
84
+ end
85
+ end
86
+
87
+ class PathSet < ::ActionView::PathSet
88
+ def find(path, prefix = nil, partial = false, details = {}, key = nil)
89
+ super
90
+ rescue ::ActionView::MissingTemplate
91
+ raise $! unless prefix =~ /^e9_crm/
92
+ super(path, "e9_crm/resources", partial, details, key)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ ActionView::Base.send(:include, E9Crm::ActionView)
@@ -0,0 +1,78 @@
1
+ module E9Crm
2
+ module TrackingController
3
+ extend ActiveSupport::Concern
4
+
5
+ included { before_filter :track_page_view }
6
+
7
+ protected
8
+
9
+ def track_page_view
10
+ @_page_view ||= tracking_cookie.page_views.create({
11
+ :request_path => request.fullpath,
12
+ :user_agent => request.user_agent,
13
+ :referer => request.referer,
14
+ :remote_ip => request.remote_ip,
15
+ :session => request.session_options[:id],
16
+ :campaign => tracking_cookie.code.presence && Campaign.find_by_code(tracking_cookie.code),
17
+ :new_visit => tracking_cookie.new_visit?
18
+ })
19
+ end
20
+
21
+ def tracking_cookie
22
+ @_tracking_cookie ||= begin
23
+ E9Crm.log "Begin load or install cookie: cookie_name[#{E9Crm.cookie_name}] query_param[#{E9Crm.query_param}]"
24
+
25
+ code = params.delete(E9Crm.query_param)
26
+
27
+ if hid = cookies[E9Crm.cookie_name]
28
+ E9Crm.log("Installed cookie found: hid(#{hid})")
29
+ @_tracking_cookie = TrackingCookie.find_by_hid(hid)
30
+
31
+ unless @_tracking_cookie
32
+ # This should only happen in developemnt, as it means the cookie has been
33
+ # installed then removed from the database.
34
+ E9Crm.log("Installed cookie's hash id does not match any stored cookies!")
35
+ end
36
+ end
37
+
38
+ E9Crm.log(@_tracking_cookie ? "Cookie loaded: (#{E9Crm.cookie_name} : #{@_tracking_cookie.hid})" : "Cookie not found")
39
+
40
+ if @_tracking_cookie
41
+ if current_user && @_tracking_cookie.user_id? && @_tracking_cookie.user_id != current_user.id
42
+ E9Crm.log "Tracking user_id not matched: found(#{@_tracking_cookie.user_id}), current(#{current_user.id}"
43
+ @_tracking_cookie = nil
44
+ else
45
+ attrs = {}
46
+
47
+ if current_user && !@_tracking_cookie.user_id?
48
+ E9Crm.log("Cookie user (#{@_tracking_cookie.user_id}) not current_user (#{current_user.id}), changing...")
49
+ attrs[:user] = current_user
50
+ end
51
+
52
+ if code.present? && code != @_tracking_cookie.code
53
+ E9Crm.log "Code present and cookie code(#{@_tracking_cookie.code}) does not match (#{code}), changing..."
54
+ attrs[:code] = code
55
+
56
+ E9Crm.log "Cookie marked as new"
57
+ attrs[:new_visit] = true
58
+ end
59
+
60
+ E9Crm.log(attrs.blank? ? "Cookie unchanged, no update" : "Cookie changed, new attrs: #{attrs.inspect}")
61
+ @_tracking_cookie.update_attributes(attrs) unless attrs.blank?
62
+ end
63
+ end
64
+
65
+ @_tracking_cookie ||= begin
66
+ TrackingCookie.create(:code => code, :user => current_user, :new_visit => true).tap do |cookie|
67
+ E9Crm.log "Installing new cookie (#{E9Crm.cookie_name} : #{cookie.hid})"
68
+ cookies.permanent[E9Crm.cookie_name] = cookie.hid
69
+ end
70
+ end
71
+
72
+ E9Crm.log("Final Cookie : #{@_tracking_cookie.inspect}")
73
+
74
+ @_tracking_cookie
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module E9Crm
2
+ VERSION = '0.1.1'
3
+ end
data/lib/e9_crm.rb ADDED
@@ -0,0 +1,59 @@
1
+ require 'rails'
2
+ require 'e9_rails'
3
+ require 'e9_tags'
4
+ require 'money'
5
+ require 'inherited_resources'
6
+ require 'will_paginate'
7
+ require 'has_scope'
8
+
9
+ require 'e9_crm/rails_extensions'
10
+
11
+ module E9Crm
12
+ autoload :VERSION, 'e9_crm/version'
13
+ autoload :TrackingController, 'e9_crm/tracking_controller'
14
+ autoload :Model, 'e9_crm/model'
15
+
16
+ mattr_accessor :cookie_name
17
+ @@cookie_name = '_e9_tc'
18
+
19
+ mattr_accessor :query_param
20
+ @@query_param = 'code'
21
+
22
+ mattr_accessor :log_level
23
+ @@log_level = :debug
24
+
25
+ mattr_accessor :logging
26
+ @@logging = false
27
+
28
+ mattr_accessor :user_model
29
+ @@user_model = nil
30
+
31
+ mattr_accessor :tracking_controllers
32
+ @@tracking_controllers = []
33
+
34
+ def E9Crm.log(message)
35
+ Rails.logger.send(@@log_level, "e9Crm: #{message}") if @@logging
36
+ end
37
+
38
+ def E9Crm.init!
39
+ user_model = case @@user_model
40
+ when Class; @@user_model
41
+ when String, Symbol; @@user_model.classify.constantize
42
+ end
43
+
44
+ if user_model
45
+ user_model.send(:include, E9Crm::Model)
46
+ end
47
+
48
+ E9Crm.tracking_controllers.each do |controller|
49
+ controller.send(:include, E9Crm::TrackingController)
50
+ end
51
+ end
52
+
53
+ class Engine < ::Rails::Engine
54
+ config.e9_crm = E9Crm
55
+ config.active_record.observers ||= []
56
+ config.active_record.observers << :deal_observer
57
+ config.to_prepare { E9Crm.init! }
58
+ end
59
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module E9Crm
5
+ module Generators
6
+ class InstallGenerator < Rails::Generators::Base
7
+ include Rails::Generators::Migration
8
+
9
+ source_root File.expand_path('templates', File.dirname(__FILE__))
10
+
11
+ def self.next_migration_number(dirname) #:nodoc:
12
+ if ActiveRecord::Base.timestamped_migrations
13
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
14
+ else
15
+ "%.3d" % (current_migration_number(dirname) + 1)
16
+ end
17
+ end
18
+
19
+ def generate_migrations
20
+ migration_template 'create_e9_crm_tables.rb', 'db/migrate/create_e9_crm_tables.rb'
21
+ end
22
+
23
+ def copy_initializer
24
+ copy_file 'initializer.rb', 'config/initializers/e9_crm.rb'
25
+ end
26
+
27
+ def copy_javascript
28
+ copy_file 'javascript.js', 'public/javascripts/e9_crm.js'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,107 @@
1
+ class CreateE9CrmTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :page_views, :force => true do |t|
4
+ t.references :tracking_cookie, :campaign
5
+ t.string :request_path, :user_agent, :referer, :limit => 200
6
+ t.string :session, :limit => 32
7
+ t.string :remote_ip, :limit => 16
8
+ t.boolean :new_visit, :default => false
9
+ t.timestamp :created_at
10
+ end
11
+ add_index 'page_views', 'tracking_cookie_id'
12
+ add_index 'page_views', 'campaign_id'
13
+
14
+ create_table :tracking_cookies, :force => true do |t|
15
+ t.references :user
16
+ t.string :hid, :code, :limit => 32
17
+ t.timestamps
18
+ end
19
+ add_index 'tracking_cookies', 'hid'
20
+ add_index 'tracking_cookies', 'code'
21
+ add_index 'tracking_cookies', 'user_id'
22
+
23
+ create_table :contacts, :force => true do |t|
24
+ t.string :type
25
+ t.string :first_name
26
+ t.string :last_name
27
+ t.string :title
28
+ t.string :avatar
29
+ t.references :company
30
+ t.timestamps
31
+ end
32
+ add_index 'contacts', 'company_id'
33
+
34
+ create_table :record_attributes, :force => true do |t|
35
+ t.string :type
36
+ t.references :record, :polymorphic => true
37
+ t.text :value, :options, :limit => 3.kilobytes
38
+ end
39
+
40
+ create_table :dated_costs, :force => true do |t|
41
+ t.references :costable, :polymorphic => true
42
+ t.integer :cost, :default => 0
43
+ t.date :date
44
+ t.timestamps
45
+ end
46
+
47
+ create_table :campaign_groups, :force => true do |t|
48
+ t.string :name
49
+ t.timestamps
50
+ end
51
+
52
+ create_table :campaigns, :force => true do |t|
53
+ t.string :type
54
+ t.string :name
55
+ t.references :campaign_group, :affiliate, :sales_person
56
+ t.string :code, :limit => 32
57
+ t.integer :affiliate_fee, :sales_fee, :default => 0
58
+ t.boolean :active, :default => true
59
+ t.timestamps
60
+ end
61
+ add_index 'campaigns', 'campaign_group_id'
62
+ add_index 'campaigns', 'code'
63
+
64
+ create_table :companies, :force => true do |t|
65
+ t.string :name
66
+ t.timestamps
67
+ end
68
+
69
+ create_table :deals, :force => true do |t|
70
+ t.string :type
71
+ t.references :offer, :campaign, :tracking_cookie
72
+ t.timestamp :created_at, :updated_at, :converted_at
73
+ t.string :status, :limit => 32
74
+ end
75
+ add_index 'deals', 'offer_id'
76
+ add_index 'deals', 'campaign_id'
77
+ add_index 'deals', 'tracking_cookie_id'
78
+ add_index 'deals', 'status'
79
+
80
+ create_table :offers, :force => true do |t|
81
+ t.string :type
82
+ t.references :offer, :campaign, :tracking_cookie
83
+ t.timestamp :created_at, :updated_at, :converted_at
84
+ t.string :status, :limit => 32
85
+ end
86
+
87
+
88
+
89
+ add_column :users, :contact_id, :integer rescue nil
90
+ add_column :users, :options, :text, :limit => 1.kilobyte rescue nil
91
+ end
92
+
93
+ def self.down
94
+ remove_column :users, :contact_id rescue nil
95
+ remove_column :users, :options rescue nil
96
+
97
+ drop_table :deals rescue nil
98
+ drop_table :companies rescue nil
99
+ drop_table :campaigns rescue nil
100
+ drop_table :campaign_groups rescue nil
101
+ drop_table :dated_costs rescue nil
102
+ drop_table :record_attributes rescue nil
103
+ drop_table :contacts rescue nil
104
+ drop_table :tracking_cookies rescue nil
105
+ drop_table :page_views rescue nil
106
+ end
107
+ end
@@ -0,0 +1,4 @@
1
+ require 'e9_crm'
2
+
3
+ E9Crm.user_model = User
4
+ E9Crm.tracking_controllers << ApplicationController