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.
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +105 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/app/controllers/e9_crm/advertising_campaigns_controller.rb +3 -0
- data/app/controllers/e9_crm/affiliate_campaigns_controller.rb +3 -0
- data/app/controllers/e9_crm/base_controller.rb +5 -0
- data/app/controllers/e9_crm/campaign_groups_controller.rb +3 -0
- data/app/controllers/e9_crm/campaigns_controller.rb +4 -0
- data/app/controllers/e9_crm/companies_controller.rb +3 -0
- data/app/controllers/e9_crm/contact_emails_controller.rb +38 -0
- data/app/controllers/e9_crm/contact_merges_controller.rb +26 -0
- data/app/controllers/e9_crm/contacts_controller.rb +46 -0
- data/app/controllers/e9_crm/dated_costs_controller.rb +4 -0
- data/app/controllers/e9_crm/deals_controller.rb +19 -0
- data/app/controllers/e9_crm/email_campaigns_controller.rb +3 -0
- data/app/controllers/e9_crm/email_templates.controller.rb +3 -0
- data/app/controllers/e9_crm/menu_options_controller.rb +26 -0
- data/app/controllers/e9_crm/offers_controller.rb +3 -0
- data/app/controllers/e9_crm/page_views_controller.rb +20 -0
- data/app/controllers/e9_crm/record_attributes_controller.rb +3 -0
- data/app/controllers/e9_crm/reports_controller.rb +2 -0
- data/app/controllers/e9_crm/resources_controller.rb +43 -0
- data/app/controllers/e9_crm/sales_campaigns_controller.rb +3 -0
- data/app/helpers/e9_crm/base_helper.rb +77 -0
- data/app/helpers/e9_crm/campaigns_helper.rb +14 -0
- data/app/helpers/e9_crm/contact_merges_helper.rb +17 -0
- data/app/helpers/e9_crm/contacts_helper.rb +27 -0
- data/app/helpers/e9_crm/deals_helper.rb +15 -0
- data/app/helpers/e9_crm/menu_options_helper.rb +11 -0
- data/app/helpers/e9_crm/page_views_helper.rb +2 -0
- data/app/models/address_attribute.rb +4 -0
- data/app/models/advertising_campaign.rb +15 -0
- data/app/models/affiliate.rb +4 -0
- data/app/models/affiliate_campaign.rb +15 -0
- data/app/models/campaign.rb +34 -0
- data/app/models/campaign_group.rb +5 -0
- data/app/models/company.rb +4 -0
- data/app/models/contact.rb +258 -0
- data/app/models/contact_email.rb +49 -0
- data/app/models/dated_cost.rb +9 -0
- data/app/models/deal.rb +83 -0
- data/app/models/email_campaign.rb +13 -0
- data/app/models/email_template.rb +7 -0
- data/app/models/instant_messaging_handle_attribute.rb +4 -0
- data/app/models/menu_option.rb +33 -0
- data/app/models/offer.rb +50 -0
- data/app/models/page_view.rb +80 -0
- data/app/models/phone_number_attribute.rb +4 -0
- data/app/models/record_attribute.rb +41 -0
- data/app/models/sales_campaign.rb +15 -0
- data/app/models/sales_person.rb +4 -0
- data/app/models/tracking_cookie.rb +61 -0
- data/app/models/website_attribute.rb +4 -0
- data/app/observers/deal_observer.rb +11 -0
- data/app/uploaders/file_uploader.rb +34 -0
- data/app/views/e9_crm/campaigns/_form_inner.html.haml +5 -0
- data/app/views/e9_crm/contact_emails/_form.html.haml +7 -0
- data/app/views/e9_crm/contact_emails/_form_inner.html.haml +11 -0
- data/app/views/e9_crm/contact_emails/destroy.js.erb +3 -0
- data/app/views/e9_crm/contact_emails/send_email.js.erb +1 -0
- data/app/views/e9_crm/contact_merges/_field.html.haml +10 -0
- data/app/views/e9_crm/contact_merges/_form.html.haml +10 -0
- data/app/views/e9_crm/contact_merges/new.html.haml +2 -0
- data/app/views/e9_crm/contacts/_details.html.haml +22 -0
- data/app/views/e9_crm/contacts/_form_inner.html.haml +51 -0
- data/app/views/e9_crm/contacts/_header.html.haml +19 -0
- data/app/views/e9_crm/contacts/_tag_table.html.haml +15 -0
- data/app/views/e9_crm/contacts/index.html.haml +13 -0
- data/app/views/e9_crm/contacts/index.js.erb +5 -0
- data/app/views/e9_crm/contacts/merge.html.haml +1 -0
- data/app/views/e9_crm/contacts/templates.js.erb +1 -0
- data/app/views/e9_crm/deals/_form_inner.html.haml +5 -0
- data/app/views/e9_crm/deals/_header.html.haml +0 -0
- data/app/views/e9_crm/deals/leads.html.haml +13 -0
- data/app/views/e9_crm/email_templates/_form_inner.html.haml +9 -0
- data/app/views/e9_crm/menu_options/_form_inner.html.haml +6 -0
- data/app/views/e9_crm/menu_options/_header.html.haml +8 -0
- data/app/views/e9_crm/offers/_form.html.haml +7 -0
- data/app/views/e9_crm/offers/_form_inner.html.haml +42 -0
- data/app/views/e9_crm/offers/_form_inner.html.haml.bak +43 -0
- data/app/views/e9_crm/page_views/_table.html.haml +25 -0
- data/app/views/e9_crm/record_attributes/_address_attribute.html.haml +5 -0
- data/app/views/e9_crm/record_attributes/_instant_messaging_handle_attribute.html.haml +5 -0
- data/app/views/e9_crm/record_attributes/_phone_number_attribute.html.haml +5 -0
- data/app/views/e9_crm/record_attributes/_record_attribute.html.haml +10 -0
- data/app/views/e9_crm/record_attributes/_templates.js.erb +12 -0
- data/app/views/e9_crm/record_attributes/_user.html.haml +23 -0
- data/app/views/e9_crm/record_attributes/_website_attribute.html.haml +5 -0
- data/app/views/e9_crm/resources/_footer.html.haml +1 -0
- data/app/views/e9_crm/resources/_form.html.haml +7 -0
- data/app/views/e9_crm/resources/_form_inner.html.haml +5 -0
- data/app/views/e9_crm/resources/_header.html.haml +0 -0
- data/app/views/e9_crm/resources/_table.html.haml +21 -0
- data/app/views/e9_crm/resources/create.js.erb +6 -0
- data/app/views/e9_crm/resources/destroy.js.erb +3 -0
- data/app/views/e9_crm/resources/edit.html.haml +2 -0
- data/app/views/e9_crm/resources/index.html.haml +13 -0
- data/app/views/e9_crm/resources/index.js.erb +1 -0
- data/app/views/e9_crm/resources/new.html.haml +2 -0
- data/app/views/e9_crm/resources/show.html.haml +2 -0
- data/app/views/e9_crm/resources/update.js.erb +5 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/locales/e9.en.yml +28 -0
- data/config/locales/en.yml +63 -0
- data/config/routes.rb +61 -0
- data/e9_crm.gemspec +29 -0
- data/lib/e9_crm/model.rb +63 -0
- data/lib/e9_crm/rails_extensions.rb +98 -0
- data/lib/e9_crm/tracking_controller.rb +78 -0
- data/lib/e9_crm/version.rb +3 -0
- data/lib/e9_crm.rb +59 -0
- data/lib/generators/e9_crm/install_generator.rb +32 -0
- data/lib/generators/e9_crm/templates/create_e9_crm_tables.rb +107 -0
- data/lib/generators/e9_crm/templates/initializer.rb +4 -0
- data/lib/generators/e9_crm/templates/javascript.js +187 -0
- data/test/functional/e9_crm/campaign_types_controller_test.rb +49 -0
- data/test/functional/home_controller_test.rb +8 -0
- metadata +283 -0
|
@@ -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,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
|
data/lib/e9_crm/model.rb
ADDED
|
@@ -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
|
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
|