e9_crm 0.1.8 → 0.1.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.md +2 -3
  2. data/app/controllers/e9_crm/advertising_campaigns_controller.rb +2 -0
  3. data/app/controllers/e9_crm/affiliate_campaigns_controller.rb +2 -0
  4. data/app/controllers/e9_crm/campaign_groups_controller.rb +8 -4
  5. data/app/controllers/e9_crm/campaign_subclass_controller.rb +15 -0
  6. data/app/controllers/e9_crm/campaigns_controller.rb +15 -23
  7. data/app/controllers/e9_crm/companies_controller.rb +11 -0
  8. data/app/controllers/e9_crm/email_campaigns_controller.rb +2 -0
  9. data/app/controllers/e9_crm/menu_options_controller.rb +2 -0
  10. data/app/controllers/e9_crm/resources_controller.rb +11 -1
  11. data/app/controllers/e9_crm/sales_campaigns_controller.rb +2 -0
  12. data/app/helpers/e9_crm/base_helper.rb +29 -0
  13. data/app/helpers/e9_crm/campaign_groups_helper.rb +1 -1
  14. data/app/helpers/e9_crm/campaigns_helper.rb +3 -4
  15. data/app/models/affiliate_campaign.rb +3 -1
  16. data/app/models/campaign.rb +7 -2
  17. data/app/models/campaign_group.rb +3 -1
  18. data/app/models/company.rb +5 -0
  19. data/app/models/contact.rb +19 -1
  20. data/app/models/menu_option.rb +4 -3
  21. data/app/models/sales_campaign.rb +3 -1
  22. data/app/views/e9_crm/affiliate_campaigns/_form_inner.html.haml +3 -3
  23. data/app/views/e9_crm/campaign_groups/_form_inner.html.haml +3 -0
  24. data/app/views/e9_crm/campaigns/_form_inner.html.haml +5 -8
  25. data/app/views/e9_crm/campaigns/_table.html.haml +2 -2
  26. data/app/views/e9_crm/companies/_header.html.haml +4 -0
  27. data/app/views/e9_crm/contacts/_header.html.haml +0 -21
  28. data/app/views/e9_crm/contacts/_index_sidebar.html.haml +1 -1
  29. data/app/views/e9_crm/contacts/show.html.haml +3 -1
  30. data/app/views/e9_crm/menu_options/_footer.html.haml +0 -0
  31. data/app/views/e9_crm/menu_options/_form_inner.html.haml +1 -1
  32. data/app/views/e9_crm/resources/_table.html.haml +5 -2
  33. data/app/views/e9_crm/sales_campaigns/_form_inner.html.haml +2 -2
  34. data/config/locales/e9.en.yml +0 -1
  35. data/config/locales/en.yml +11 -1
  36. data/e9_crm.gemspec +1 -0
  37. data/lib/e9_crm/e9_extensions.rb +20 -0
  38. data/lib/e9_crm/email.rb +16 -0
  39. data/lib/e9_crm/rails_extensions.rb +32 -1
  40. data/lib/e9_crm/version.rb +1 -1
  41. data/lib/e9_crm.rb +5 -0
  42. metadata +20 -5
data/README.md CHANGED
@@ -1,9 +1,8 @@
1
+ *NOTE his plugin requires the private e9_base CMS gem and WILL NOT WORK without it.*
2
+
1
3
  CRM Plugin for the e9 CMS
2
4
  =========================
3
5
 
4
- It should be noted that while this plugin is intended for use in the
5
- e9 CMS, it does neither includes nor requires it.
6
-
7
6
  To use, add as a gem and install by running:
8
7
 
9
8
  rails g e9_crm:install
@@ -1,4 +1,6 @@
1
1
  class E9Crm::AdvertisingCampaignsController < E9Crm::CampaignSubclassController
2
2
  defaults :resource_class => AdvertisingCampaign
3
3
  include E9Rails::Controllers::Orderable
4
+
5
+ helper 'e9_crm/campaigns'
4
6
  end
@@ -1,4 +1,6 @@
1
1
  class E9Crm::AffiliateCampaignsController < E9Crm::CampaignSubclassController
2
2
  defaults :resource_class => AffiliateCampaign
3
3
  include E9Rails::Controllers::Orderable
4
+
5
+ helper 'e9_crm/campaigns'
4
6
  end
@@ -1,11 +1,15 @@
1
1
  class E9Crm::CampaignGroupsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => CampaignGroup
3
3
  include E9Rails::Controllers::Orderable
4
+ self.should_paginate_index = false
4
5
 
5
6
  protected
6
-
7
- # no pagination
8
- def collection
9
- get_collection_ivar || set_collection_ivar(end_of_association_chain.all)
7
+
8
+ def default_ordered_on
9
+ 'name'
10
+ end
11
+
12
+ def default_ordered_dir
13
+ 'ASC'
10
14
  end
11
15
  end
@@ -1,6 +1,21 @@
1
1
  class E9Crm::CampaignSubclassController < E9Crm::ResourcesController
2
2
 
3
+ def update
4
+ update! { parent_redirect_path }
5
+ end
6
+
7
+ def create
8
+ create! { parent_redirect_path }
9
+ end
10
+
3
11
  protected
12
+ def parent_redirect_path
13
+ campaigns_path(:type => type_param)
14
+ end
15
+
16
+ def type_param
17
+ resource_class.name[/(.*)Campaign/, 1].underscore
18
+ end
4
19
 
5
20
  def add_index_breadcrumb
6
21
  add_breadcrumb! Campaign.model_name.collection.titleize, campaigns_path
@@ -2,11 +2,11 @@ class E9Crm::CampaignsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Campaign
3
3
  include E9Rails::Controllers::Orderable
4
4
 
5
- filter_access_to :reports, :require => :read, :context => :admin
5
+ self.should_paginate_index = false
6
6
 
7
7
  has_scope :of_group, :as => :group, :only => :index
8
8
 
9
- has_scope :active, :only => :index do |_, scope, value|
9
+ has_scope :active, :only => :index, :default => 'true' do |_, scope, value|
10
10
  scope.active(E9.true_value?(value))
11
11
  end
12
12
 
@@ -14,31 +14,23 @@ class E9Crm::CampaignsController < E9Crm::ResourcesController
14
14
  scope.of_type("#{value}_campaign".classify)
15
15
  end
16
16
 
17
- def reports
18
- index!
19
- end
20
-
21
17
  protected
22
18
 
23
- def set_reports_index_title
24
- @index_title = I18n.t(:reports_title, :scope => 'e9.e9_crm.campaigns')
19
+ def collection_scope
20
+ end_of_association_chain.typed.includes(:campaign_group)
21
+
22
+ # NOTE this is a pretty ugly join just to be able to sort on campaign group name
23
+ end_of_association_chain.typed
24
+ .joins("left outer join campaign_groups on campaign_groups.id = campaigns.campaign_group_id")
25
+ .select("campaigns.*, campaign_groups.name campaign_group_name")
25
26
  end
26
27
 
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
- else
34
- # don't include NoCampaign normally
35
- scope = scope.typed
36
- end
37
- scope
28
+ def default_ordered_on
29
+ 'campaign_group_name,name'
38
30
  end
39
-
40
- # no pagination
41
- def collection
42
- get_collection_ivar || set_collection_ivar(collection_scope.all)
31
+
32
+ def default_ordered_dir
33
+ 'ASC'
43
34
  end
35
+
44
36
  end
@@ -1,4 +1,15 @@
1
1
  class E9Crm::CompaniesController < E9Crm::ResourcesController
2
2
  defaults :resource_class => Company
3
3
  include E9Rails::Controllers::Orderable
4
+ include E9::DestroyRestricted::Controller
5
+
6
+ protected
7
+
8
+ def default_ordered_on
9
+ 'name'
10
+ end
11
+
12
+ def default_ordered_dir
13
+ 'ASC'
14
+ end
4
15
  end
@@ -1,4 +1,6 @@
1
1
  class E9Crm::EmailCampaignsController < E9Crm::CampaignSubclassController
2
2
  defaults :resource_class => EmailCampaign
3
3
  include E9Rails::Controllers::Orderable
4
+
5
+ helper 'e9_crm/campaigns'
4
6
  end
@@ -2,6 +2,8 @@ class E9Crm::MenuOptionsController < E9Crm::ResourcesController
2
2
  defaults :resource_class => MenuOption
3
3
  include E9Rails::Controllers::Sortable
4
4
 
5
+ self.should_paginate_index = false
6
+
5
7
  has_scope :options_for, :as => :key, :only => :index
6
8
 
7
9
  # NOTE The reason this is set in a filter instead of just a default scope value
@@ -1,6 +1,12 @@
1
1
  class E9Crm::ResourcesController < E9Crm::BaseController
2
2
  include E9Rails::Helpers::ResourceErrorMessages
3
- include E9Rails::Helpers::Pagination
3
+
4
+ # NOTE depending on e9_base pagination (which should eventually use this module)
5
+ #include E9Rails::Helpers::Pagination
6
+
7
+ # TODO implement role on e9_crm models?
8
+ #include E9::Roles::Controller
9
+ #filter_access_to :update, :edit, :attribute_check => true, :load_method => :filter_target, :context => :admin
4
10
 
5
11
  class_inheritable_accessor :should_paginate_index
6
12
  self.should_paginate_index = true
@@ -25,6 +31,10 @@ class E9Crm::ResourcesController < E9Crm::BaseController
25
31
 
26
32
  protected
27
33
 
34
+ def filter_target
35
+ resource
36
+ end
37
+
28
38
  # NOTE parent is defined so it's always available, it will be overridden on controllers which have belongs_to routes
29
39
  def parent; end
30
40
  helper_method :parent
@@ -1,4 +1,6 @@
1
1
  class E9Crm::SalesCampaignsController < E9Crm::CampaignSubclassController
2
2
  defaults :resource_class => SalesCampaign
3
3
  include E9Rails::Controllers::Orderable
4
+
5
+ helper 'e9_crm/campaigns'
4
6
  end
@@ -1,5 +1,34 @@
1
1
  module E9Crm::BaseHelper
2
2
 
3
+ def kramdown(string)
4
+ Kramdown::Document.new(string).to_html.html_safe
5
+ end
6
+
7
+ alias :k :kramdown
8
+
9
+ def help_tooltip(string)
10
+ return <<-HTML.strip.html_safe
11
+ <span class="help" rel="tooltip" title="#{CGI.escape_html(string)}">#{t(:inline_help_link)}</span>
12
+ HTML
13
+ end
14
+
15
+ def help_label(form_or_id, key, options = {})
16
+ options[:key] ||= :"#{key}_help"
17
+
18
+ help_title = options.delete(:title) || resource_humanize(options.delete(:key))
19
+
20
+ str = ''.html_safe
21
+ str.safe_concat resource_humanize(key)
22
+ str.safe_concat ' '
23
+ str.safe_concat help_tooltip(help_title)
24
+
25
+ if form_or_id.respond_to?(:label)
26
+ form_or_id.label(key, str, options)
27
+ else
28
+ label_tag(form_or_id, str, options)
29
+ end
30
+ end
31
+
3
32
  ##
4
33
  # Field maps
5
34
  #
@@ -1,7 +1,7 @@
1
1
  module E9Crm::CampaignGroupsHelper
2
2
  def records_table_field_map_for_campaign_group
3
3
  {
4
- :fields => { :name => nil },
4
+ :fields => { 'name' => nil },
5
5
  :links => proc {|r| [link_to_edit_resource(r), link_to_destroy_resource(r)] }
6
6
  }
7
7
  end
@@ -4,7 +4,7 @@ module E9Crm::CampaignsHelper
4
4
  end
5
5
 
6
6
  def display_campaign_code(val)
7
- val || 'n/a'
7
+ val && "?#{E9Crm.query_param}=#{val}" || 'n/a'
8
8
  end
9
9
 
10
10
  def display_campaign_type(val)
@@ -18,7 +18,7 @@ module E9Crm::CampaignsHelper
18
18
  end
19
19
 
20
20
  def campaign_group_select_options
21
- options = CampaignGroup.select('name, id').all.map {|c| [c.name, c.id] }
21
+ options = CampaignGroup.select('name, id').ordered.all.map {|c| [c.name, c.id] }
22
22
  options.unshift(['All Groups', nil])
23
23
  options_for_select(options)
24
24
  end
@@ -29,8 +29,7 @@ module E9Crm::CampaignsHelper
29
29
 
30
30
  def campaign_active_select_options
31
31
  options = [
32
- ['All Statuses', nil],
33
- ['Active', true],
32
+ ['Active', true],
34
33
  ['Inactive', false]
35
34
  ]
36
35
  options_for_select(options)
@@ -4,7 +4,9 @@
4
4
  #
5
5
  class AffiliateCampaign < SalesCampaign
6
6
  money_columns :affiliate_fee
7
- belongs_to :affiliate
7
+
8
+ belongs_to :affiliate, :class_name => 'Contact'
9
+ validates :affiliate, :presence => true
8
10
 
9
11
  ##
10
12
  # The sum cost of this campaign
@@ -26,9 +26,14 @@ class Campaign < ActiveRecord::Base
26
26
  NoCampaign.first || NoCampaign.create
27
27
  end
28
28
 
29
+ validates :name, :presence => true,
30
+ :uniqueness => { :allow_blank => true, :case_sensitive => false }
31
+
29
32
  validates :code, :presence => { :unless => lambda {|r| r.is_a?(NoCampaign) } },
30
- :length => { :maximum => 32 },
31
- :uniqueness => { :ignore_case => true, :allow_blank => true }
33
+ :length => { :maximum => 32 },
34
+ :format => { :allow_blank => true, :with => /^[\w\d]+$/ },
35
+ :uniqueness => { :case_sensitive => false, :allow_blank => true }
36
+
32
37
  validates :affiliate_fee, :numericality => true
33
38
  validates :sales_fee, :numericality => true
34
39
 
@@ -3,7 +3,9 @@
3
3
  class CampaignGroup < ActiveRecord::Base
4
4
  has_many :campaigns
5
5
 
6
- validates :name, :uniqueness => { :ignore_case => true }
6
+ validates :name, :presence => true, :uniqueness => { :allow_nil => true, :case_sensitive => false }
7
+
8
+ scope :ordered, lambda { order(arel_table[:name].asc) }
7
9
 
8
10
  def to_s
9
11
  name
@@ -2,4 +2,9 @@
2
2
  #
3
3
  class Company < ActiveRecord::Base
4
4
  include E9Rails::ActiveRecord::AttributeSearchable
5
+
6
+ has_many :contacts, :dependent => :restrict
7
+
8
+ validates :name, :presence => true, :uniqueness => { :allow_blank => true, :case_sensitive => false }
9
+
5
10
  end
@@ -8,12 +8,13 @@ class Contact < ActiveRecord::Base
8
8
  include Rails.application.routes.url_helpers
9
9
 
10
10
  before_validation :ensure_user_references
11
+ before_destroy :ensure_no_associated_deals
11
12
 
12
13
  ##
13
14
  # Associations
14
15
  #
15
16
  belongs_to :company
16
- has_many :owned_deals, :class_name => 'Deal'
17
+ has_many :owned_deals, :class_name => 'Deal', :dependent => :restrict
17
18
  has_and_belongs_to_many :associated_deals, :class_name => 'Deal'
18
19
 
19
20
  has_many :users, :inverse_of => :contact, :dependent => :nullify do
@@ -83,6 +84,8 @@ class Contact < ActiveRecord::Base
83
84
  end
84
85
  accepts_nested_attributes_for :users, :allow_destroy => true
85
86
 
87
+ delegate :email, :to => 'users.primary.first', :allow_nil => true
88
+
86
89
  def page_views
87
90
  PageView.by_user(users)
88
91
  end
@@ -149,6 +152,7 @@ class Contact < ActiveRecord::Base
149
152
  scope :sales_persons, lambda { where(:status => Status::SalesPerson) }
150
153
  scope :affiliates, lambda { where(:status => Status::Affiliate) }
151
154
  scope :contacts, lambda { where(:status => Status::Contact) }
155
+ scope :ordered, lambda { order(arel_table[:first_name].asc) }
152
156
 
153
157
  scope :by_title, lambda {|val| where(:title => val) }
154
158
  scope :by_company, lambda {|val| where(:company_id => val) }
@@ -284,6 +288,10 @@ class Contact < ActiveRecord::Base
284
288
  end
285
289
  end
286
290
 
291
+ def to_liquid
292
+ Drop.new(self)
293
+ end
294
+
287
295
  protected
288
296
 
289
297
  def _assign_initialization_defaults
@@ -307,6 +315,16 @@ class Contact < ActiveRecord::Base
307
315
  attributes.keys.member?('email') && attributes['email'].blank?
308
316
  end
309
317
 
318
+ def ensure_no_associated_deals
319
+ unless self.associated_deals.empty?
320
+ object.errors.add(:associated_deals, :delete_restricted)
321
+ end
322
+ end
323
+
324
+ class Drop < ::E9::Liquid::Drops::Base
325
+ source_methods :first_name, :last_name, :name, :email, :title, :company_name
326
+ end
327
+
310
328
  module Status
311
329
  VALUES = %w(contact sales_person affiliate)
312
330
  Contact = VALUES[0]
@@ -2,12 +2,13 @@
2
2
  #
3
3
  class MenuOption < ActiveRecord::Base
4
4
  KEYS = [
5
+ #'Task Category',
6
+ #'Task Status',
7
+ 'Address',
5
8
  'Deal Category',
6
9
  'Email',
7
10
  'Instant Messaging Handle',
8
11
  'Phone Number',
9
- #'Task Category',
10
- #'Task Status',
11
12
  'Website'
12
13
  ].freeze
13
14
 
@@ -16,7 +17,7 @@ class MenuOption < ActiveRecord::Base
16
17
 
17
18
  acts_as_list :scope => 'menu_options.key = \"#{key}\"'
18
19
 
19
- scope :options_for, lambda {|key| where(:key => key) }
20
+ scope :options_for, lambda {|key| where(:key => key).order('menu_options.position ASC') }
20
21
 
21
22
  ##
22
23
  # A direct SQL selection of values for a given key
@@ -4,7 +4,9 @@
4
4
  #
5
5
  class SalesCampaign < Campaign
6
6
  money_columns :sales_fee
7
- belongs_to :sales_person
7
+
8
+ belongs_to :sales_person, :class_name => 'Contact'
9
+ validates :sales_person, :presence => true
8
10
 
9
11
  ##
10
12
  # The sum cost of this campaign
@@ -3,8 +3,8 @@
3
3
  %fieldset
4
4
  %legend= e9_t(:affiliate_information_legend, :scope => 'e9_crm.campaigns')
5
5
  .field
6
- = f.label :affilate_id
7
- = f.collection_select :affiliate_id, Contact.affiliates.all, :id, :name, :prompt => true
6
+ = f.label :affilate, nil, :class => :req, :for => 'campaign_affiliate_id'
7
+ = f.collection_select :affiliate_id, Contact.affiliates.ordered.all, :id, :name, {:prompt => true}, :id => 'campaign_affiliate_id'
8
8
  .field
9
- = f.label :affiliate_fee
9
+ = help_label f, :affiliate_fee
10
10
  = f.text_field :affiliate_fee
@@ -0,0 +1,3 @@
1
+ .field
2
+ = f.label :name, nil, :class => :req
3
+ = f.text_field :name
@@ -2,20 +2,17 @@
2
2
  %legend= e9_t(:campaign_information_legend, :scope => 'e9_crm.campaigns')
3
3
  .field
4
4
  = f.label :campaign_group_id
5
- = f.collection_select :campaign_group_id, CampaignGroup.all, :id, :name, :include_blank => 'No Group'
5
+ = f.collection_select :campaign_group_id, CampaignGroup.ordered.all, :id, :name, :include_blank => 'No Group'
6
6
  .field
7
- = f.label :name
7
+ = f.label :name, nil, :class => :req
8
8
  = f.text_field :name
9
9
  - if f.object.new_record?
10
10
  .field#campaign_code_field
11
- = f.label :code
11
+ = f.label :code, nil, :class => :req
12
12
  = f.text_field :code
13
13
  .field
14
- %label{:for => 'campaign_code_hint'}
15
- = resource_class.human_attribute_name(:code_hint)
16
- %span.help{:title => e9_t(:code_help, :scope => 'e9_crm.campaigns', :code => E9Crm.query_param)}= t(:inline_help_link)
17
- #campaign_code_hint= "?#{E9Crm.query_param}=#{f.object.code}"
14
+ = help_label :campaign_code_hint, :code_hint, :title => t('activerecord.attributes.campaign.code_hint_help', :code => E9Crm.query_param)
15
+ #campaign_code_hint= display_campaign_code(f.object.code || '')
18
16
  .field
19
17
  = f.label :active
20
18
  = f.check_box :active
21
-
@@ -3,7 +3,7 @@
3
3
  %tr
4
4
  %th= orderable_column_link(:type)
5
5
  %th= orderable_column_link(:name)
6
- %th= orderable_column_link('campaign_group.name', :campaign_group)
6
+ %th= orderable_column_link('campaign_group_name', :campaign_group)
7
7
  %th= orderable_column_link(:code)
8
8
  %th= orderable_column_link(:affiliate_fee)
9
9
  %th= orderable_column_link(:sales_fee)
@@ -20,7 +20,7 @@
20
20
  %td.record-name
21
21
  = record.name
22
22
  %td.record-campaign-group
23
- = record.campaign_group || e9_t(:no_group, :scope => 'e9_crm.campaigns')
23
+ = record.campaign_group_name || e9_t(:no_group, :scope => 'e9_crm.campaigns')
24
24
  %td.record-code
25
25
  = display_campaign_code(record.code)
26
26
  %td.record-affiliate-fee
@@ -0,0 +1,4 @@
1
+ .toolbar
2
+ .toolbar-right
3
+ = link_to_new_resource(Company)
4
+ = link_to_collection(Contact)
@@ -1,21 +0,0 @@
1
- .toolbar
2
- .toolbar-left
3
- = form_tag(resource_class, :method => :get, :id => 'contact_search_form') do
4
- = label_tag 'contact_search_field', t(:search)
5
- = text_field_tag 'search', params[:search], :id => 'contact_search_field'
6
- = submit_tag t(:go), :name => nil
7
- = submit_tag t(:clear), :name => nil, :id => 'contact_search_clear'
8
- .toolbar-middle
9
- - if (tag = contact_email_template_select_tag).present?
10
- = form_tag new_contact_email_path, :method => :get, :id => 'contact_email_form', 'data-empty' => e9_t(:no_contacts_notification), 'data-count' => @contact_ids.length do
11
- = tag
12
- = hidden_field_tag 'uids', @contact_ids.join(','), :id => 'contact_email_uids'
13
- = submit_tag e9_t(:send_email_template), :name => nil
14
- - if (tag = contact_newsletter_select_tag).present?
15
- = form_tag send_email_admin_user_email_path('__ID__'), :method => :put, :id => 'contact_newsletter_form', 'data-confirm' => e9_t(:contact_newsletter_confirmation, :count => collection.length), 'data-empty' => e9_t(:no_contacts_notification), 'data-count' => @contact_ids.length do
16
- = tag
17
- = hidden_field_tag 'uids', @contact_ids.join(','), :id => 'contact_newsletter_uids'
18
- = submit_tag e9_t(:send_email_newsletter), :name => nil
19
- .toolbar-right
20
- = link_to_new_resource(Contact)
21
- = link_to_new_resource(Company)
@@ -1,6 +1,6 @@
1
1
  .actions
2
2
  = link_to_new_resource(Contact)
3
- = link_to_new_resource(Company)
3
+ = link_to_collection(Company)
4
4
 
5
5
  -# Email actions (template, newsletter)
6
6
  - etag, ntag = contact_email_template_select_tag, contact_newsletter_select_tag
@@ -15,10 +15,12 @@
15
15
  - if company = resource.company
16
16
  .contact-company
17
17
  %label= company.name
18
+
18
19
  .contact-company-links.actions
19
20
  = google_search_link(company.name)
20
21
  = google_news_link(company.name)
22
+
21
23
  - if company.info.present?
22
- = contact_simple_format(company.info)
24
+ = k(company.info)
23
25
  .contact-sidebar
24
26
  = render 'sidebar'
File without changes
@@ -2,5 +2,5 @@
2
2
  = f.label :key
3
3
  = f.select :key, MenuOption::KEYS.sort
4
4
  .field
5
- = f.label :value
5
+ = f.label :value, nil, :class => :req
6
6
  = f.text_field :value
@@ -3,8 +3,11 @@
3
3
  %table.records{:class => sortable_controller? ? 'sortable' : nil }
4
4
  %thead
5
5
  %tr
6
- - field_map[:fields].keys.each do |field|
7
- %th= resource_class.human_attribute_name(field)
6
+ - field_map[:fields].keys.each do |key|
7
+ - if key.is_a?(Symbol)
8
+ %th= resource_class.human_attribute_name(key)
9
+ - else
10
+ %th= orderable_column_link(key)
8
11
  %th= e9_t(:actions)
9
12
  %tbody
10
13
  - if collection.empty?
@@ -4,8 +4,8 @@
4
4
  %legend= e9_t(:salesperson_information_legend, :scope => 'e9_crm.campaigns')
5
5
  .field
6
6
  = f.label :sales_person_id
7
- = f.collection_select :sales_person_id, Contact.sales_persons.all, :id, :name, :prompt => true
7
+ = f.collection_select :sales_person_id, Contact.sales_persons.ordered.all, :id, :name, :prompt => true
8
8
  .field
9
- = f.label :sales_fee
9
+ = help_label f, :sales_fee
10
10
  = f.text_field :sales_fee
11
11
 
@@ -38,4 +38,3 @@ en:
38
38
  campaign_information_legend: Campaign Information
39
39
  salesperson_information_legend: Salesperson Information
40
40
  affiliate_information_legend: Affiliate Information
41
- code_help: "Be careful when creating tracking codes. They are NOT EDITABLE after you save them.&#10;&#10;To track the success of this campaign, you can append ?%{code}={Your Code} to the end of any and all URLs on your website.&#10;&#10;It might look something like this: http://www.example.com/about-us?%{code}=alpha, where \"alpha\" represents the code you created.&#10;&#10;You can provide this tracking URL to a salesperson or an affiliate or it can be placed at the end of a link in any of your advertisements."
@@ -36,6 +36,11 @@ en:
36
36
  merge_required: 'Merge Required! <a href="%{merge_path}">MERGE ME</a>.'
37
37
 
38
38
  models:
39
+ campaign:
40
+ not_a_number: "%{attribute} must be a number (maximum 2 decimal places)."
41
+ attributes:
42
+ code:
43
+ invalid: "%{attribute} must be comprised of letters and numbers only."
39
44
  contact:
40
45
  attributes:
41
46
  users:
@@ -44,7 +49,7 @@ en:
44
49
  contact_email:
45
50
  attributes:
46
51
  contact_ids:
47
- blank: "You cannot send an emailwith no recipients!"
52
+ blank: "You cannot send an email with no recipients!"
48
53
  user_ids:
49
54
  blank: "None of the contacts selected are configured with primary emails. Please correct this and try again."
50
55
  deal:
@@ -86,6 +91,11 @@ en:
86
91
  campaign:
87
92
  name: Name
88
93
  code_hint: Append to URL
94
+ code_hint_help: "Be careful when creating tracking codes. They are NOT EDITABLE after you save them.\n\nTo track the success of this campaign, you can append ?%{code}={Your Code} to the end of any and all URLs on your website.\n\nIt might look something like this: http://www.example.com/about-us?%{code}=alpha, where \"alpha\" represents the code you created.\n\nYou can provide this tracking URL to a salesperson or an affiliate or it can be placed at the end of a link in any of your advertisements."
95
+ affiliate_fee: Affiliate Fee
96
+ affiliate_fee_help: This is the fee payable for each lead this affiliate sends you where it converts to a sale.
97
+ sales_fee: Sales Fee
98
+ sales_fee_help: This is the fee payable for each lead this salesperson sends you where it converts to a sale.
89
99
  contact:
90
100
  avatar: Photo
91
101
  users: Email Addresses
data/e9_crm.gemspec CHANGED
@@ -26,4 +26,5 @@ Gem::Specification.new do |s|
26
26
  s.add_dependency("e9_rails", "~> 0.0.15")
27
27
  s.add_dependency("e9_tags", "~> 0.0.11")
28
28
  s.add_dependency("will_paginate")
29
+ s.add_dependency("kramdown", "~> 0.13")
29
30
  end
@@ -0,0 +1,20 @@
1
+ module E9Crm
2
+ #
3
+ # Extends E9Base's Email to attempt to embed the Contact
4
+ # for the recipient in the liquid template arguments.
5
+ #
6
+ module Email
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ def locals_with_contact
11
+ default_locals.merge({
12
+ :contact => recipient.try(:contact)
13
+ })
14
+ end
15
+
16
+ alias :default_locals :this_locals
17
+ alias :this_locals :locals_with_contact
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module E9Crm
2
+ module Email
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def locals_with_contact
7
+ default_locals.merge({
8
+ :contact => recipient.try(:contact)
9
+ })
10
+ end
11
+
12
+ alias :default_locals :this_locals
13
+ alias :this_locals :locals_with_contact
14
+ end
15
+ end
16
+ end
@@ -16,6 +16,37 @@ class Array
16
16
  end
17
17
  end
18
18
 
19
+
20
+ #
21
+ # Money is too clever by half and attempts to deal with parsing Strings into
22
+ # currency e.g.
23
+ #
24
+ # "asdf 0.23 asdf".to_money #=> #<Money cents:23 currency:USD>
25
+ #
26
+ # This is problematic if you want to actually validate money as it parses most
27
+ # bogus strings to zero, so unless you want to validate > 0, most any input
28
+ # will pass validation.
29
+ #
30
+ # This silly hack prevents "asdf".to_money from parsing to 0 cents, instead
31
+ # returning itself. The #cents method allows for our composed_of column to
32
+ # still work, simply returning self, which would fail a numericality validation.
33
+ #
34
+ require 'money'
35
+
36
+ class String
37
+ def to_money(currency = nil)
38
+ if match /^$|[^\$\d\.\,]/
39
+ self
40
+ else
41
+ Money.parse(self, currency)
42
+ end
43
+ end
44
+
45
+ def cents
46
+ self
47
+ end
48
+ end
49
+
19
50
  class ActiveRecord::Base
20
51
  #
21
52
  # Basic conversion for "money" columns using the Money class and Rails composed_of
@@ -26,7 +57,7 @@ class ActiveRecord::Base
26
57
  composed_of :#{column_name},
27
58
  :class_name => 'Money',
28
59
  :mapping => %w(#{column_name} cents),
29
- :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : Money.empty }
60
+ :converter => Proc.new {|v| v.respond_to?(:to_money) ? v.to_money : v }
30
61
  EVAL
31
62
  end
32
63
  end
@@ -1,3 +1,3 @@
1
1
  module E9Crm
2
- VERSION = '0.1.8'
2
+ VERSION = '0.1.10'
3
3
  end
data/lib/e9_crm.rb CHANGED
@@ -5,6 +5,8 @@ require 'money'
5
5
  require 'inherited_resources'
6
6
  require 'will_paginate'
7
7
  require 'has_scope'
8
+ require 'kramdown'
9
+ require 'e9_base'
8
10
 
9
11
  require 'e9_crm/rails_extensions'
10
12
 
@@ -12,6 +14,7 @@ module E9Crm
12
14
  autoload :VERSION, 'e9_crm/version'
13
15
  autoload :Controller, 'e9_crm/controller'
14
16
  autoload :Model, 'e9_crm/model'
17
+ autoload :Email, 'e9_crm/email'
15
18
  autoload :TrackingController, 'e9_crm/tracking_controller'
16
19
 
17
20
  mattr_accessor :cookie_name
@@ -52,6 +55,8 @@ module E9Crm
52
55
 
53
56
  ActionController::Base.send(:include, E9Crm::Controller)
54
57
 
58
+ ::Email.send(:include, E9Crm::Email)
59
+
55
60
  E9Crm.tracking_controllers.each do |controller|
56
61
  controller.send(:include, E9Crm::TrackingController)
57
62
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: e9_crm
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.8
5
+ version: 0.1.10
6
6
  platform: ruby
7
7
  authors:
8
8
  - Travis Cox
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-05-25 00:00:00 -04:00
13
+ date: 2011-05-26 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -90,13 +90,23 @@ dependencies:
90
90
  version: "0"
91
91
  type: :runtime
92
92
  version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: kramdown
95
+ prerelease: false
96
+ requirement: &id008 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: "0.13"
102
+ type: :runtime
103
+ version_requirements: *id008
93
104
  description: |
105
+ *NOTE his plugin requires the private e9_base CMS gem and WILL NOT WORK without it.*
106
+
94
107
  CRM Plugin for the e9 CMS
95
108
  =========================
96
109
 
97
- It should be noted that while this plugin is intended for use in the
98
- e9 CMS, it does neither includes nor requires it.
99
-
100
110
  To use, add as a gem and install by running:
101
111
 
102
112
  rails g e9_crm:install
@@ -207,6 +217,7 @@ files:
207
217
  - app/views/e9_crm/advertising_campaigns/costs.html.haml
208
218
  - app/views/e9_crm/affiliate_campaigns/_form_inner.html.haml
209
219
  - app/views/e9_crm/campaign_groups/_footer.html.haml
220
+ - app/views/e9_crm/campaign_groups/_form_inner.html.haml
210
221
  - app/views/e9_crm/campaign_groups/_header.html.haml
211
222
  - app/views/e9_crm/campaigns/_footer.html.haml
212
223
  - app/views/e9_crm/campaigns/_form_inner.html.haml
@@ -215,6 +226,7 @@ files:
215
226
  - app/views/e9_crm/campaigns/_table.html.haml
216
227
  - app/views/e9_crm/campaigns/reports.html.haml
217
228
  - app/views/e9_crm/companies/_form_inner.html.haml
229
+ - app/views/e9_crm/companies/_header.html.haml
218
230
  - app/views/e9_crm/contact_emails/_form.html.haml
219
231
  - app/views/e9_crm/contact_emails/_form_inner.html.haml
220
232
  - app/views/e9_crm/contact_emails/destroy.js.erb
@@ -268,6 +280,7 @@ files:
268
280
  - app/views/e9_crm/leads/_form.html.haml
269
281
  - app/views/e9_crm/leads/create.js.erb
270
282
  - app/views/e9_crm/leads/new.html.haml
283
+ - app/views/e9_crm/menu_options/_footer.html.haml
271
284
  - app/views/e9_crm/menu_options/_form_inner.html.haml
272
285
  - app/views/e9_crm/menu_options/_header.html.haml
273
286
  - app/views/e9_crm/new_content_subscription_offers/_form_inner.html.haml
@@ -310,6 +323,8 @@ files:
310
323
  - e9_crm.gemspec
311
324
  - lib/e9_crm.rb
312
325
  - lib/e9_crm/controller.rb
326
+ - lib/e9_crm/e9_extensions.rb
327
+ - lib/e9_crm/email.rb
313
328
  - lib/e9_crm/model.rb
314
329
  - lib/e9_crm/rails_extensions.rb
315
330
  - lib/e9_crm/tracking_controller.rb