e9_crm 0.1.12 → 0.1.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,29 @@ class E9Crm::EmailTemplatesController < E9Crm::ResourcesController
3
3
  include E9Rails::Controllers::Orderable
4
4
  self.should_paginate_index = false
5
5
 
6
+ filter_access_to :select, :require => :read, :context => :admin
7
+
8
+ before_filter :handle_unacceptable_mimetype, :only => :show
9
+
10
+ respond_to :json, :only => :show
11
+ respond_to :html, :except => :show
12
+
13
+ def select
14
+ index!
15
+ end
16
+
17
+ def show
18
+ unless params[:contact_id] =~ /\d+/ && @contact = Contact.find_by_id(params[:contact_id])
19
+ head :status => 404
20
+ else
21
+ object = resource
22
+ object.contact = @contact
23
+ object.recipient = params[:user_id] =~ /\d+/ && @contact.users.find_by_id(params[:user_id]) || @contact.primary_user
24
+
25
+ render :json => object
26
+ end
27
+ end
28
+
6
29
  protected
7
30
 
8
31
  def default_ordered_on
@@ -12,4 +35,8 @@ class E9Crm::EmailTemplatesController < E9Crm::ResourcesController
12
35
  def default_ordered_dir
13
36
  'ASC'
14
37
  end
38
+
39
+ def determine_template
40
+ request.xhr? ? false : super
41
+ end
15
42
  end
@@ -13,7 +13,6 @@ class Campaign < ActiveRecord::Base
13
13
  has_many :lost_deals, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Lost]
14
14
  has_many :pending_deals, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Pending]
15
15
  has_many :leads, :class_name => 'Deal', :conditions => ['deals.status = ?', Deal::Status::Lead]
16
-
17
16
  has_many :page_views, :inverse_of => :campaign, :dependent => :nullify
18
17
 
19
18
  # only advertising campaigns use this association
@@ -41,6 +40,7 @@ class Campaign < ActiveRecord::Base
41
40
  scope :inactive, lambda { active(false) }
42
41
  scope :of_group, lambda {|val| where(:campaign_group_id => val.to_param) }
43
42
  scope :typed, lambda { where(arel_table[:type].not_eq('NoCampaign')) }
43
+ scope :ordered, lambda { order(arel_table[:name].asc) }
44
44
 
45
45
  def new_visit_session_count
46
46
  page_views.new_visits.group(:session).count.keys.length
@@ -67,6 +67,6 @@ class Campaign < ActiveRecord::Base
67
67
  end
68
68
 
69
69
  def to_s
70
- name
70
+ name.tap {|n| n << " (#{code})" if code.present? }
71
71
  end
72
72
  end
@@ -84,7 +84,11 @@ class Contact < ActiveRecord::Base
84
84
  end
85
85
  accepts_nested_attributes_for :users, :allow_destroy => true
86
86
 
87
- delegate :email, :to => 'users.primary.first', :allow_nil => true
87
+ def primary_user
88
+ users.primary.first
89
+ end
90
+
91
+ delegate :email, :to => :primary_user, :allow_nil => true
88
92
 
89
93
  def page_views
90
94
  PageView.by_user(users)
@@ -301,7 +305,14 @@ class Contact < ActiveRecord::Base
301
305
  end
302
306
 
303
307
  def ensure_user_references
304
- users.each {|u| u.contact = self }
308
+ users.each {|u|
309
+ # We set contact as self on all our users so they have a reference to the contact.
310
+ # This doesn't happen with nested associations by default.
311
+ u.contact = self
312
+
313
+ # make sure our users have our first name if it's blank
314
+ u.first_name = self.first_name if u.first_name.blank?
315
+ }
305
316
  end
306
317
 
307
318
  # override has_destroy_flag? to force destroy on persisted associations as well
data/app/models/deal.rb CHANGED
@@ -18,6 +18,7 @@ class Deal < ActiveRecord::Base
18
18
  money_columns :value
19
19
 
20
20
  validates :value, :numericality => true
21
+ validates :campaign, :presence => true
21
22
 
22
23
  # non-lead validations (deals in the admin)
23
24
  # require a deal name
@@ -29,11 +30,14 @@ class Deal < ActiveRecord::Base
29
30
  validates :lead_email, :presence => { :if => lambda {|r| r.lead? } },
30
31
  :email => { :if => lambda {|r| r.lead? }, :allow_blank => true }
31
32
 
33
+ # NOTE should offer be validated?
34
+ #validates :offer, :presence => { :if => lambda {|r| r.lead? } }
35
+
32
36
  # If a lead with a user, get the lead_name and lead_email from the user before validation
33
- before_validation :get_name_and_email_from_user, :only => :create
34
- before_validation :update_to_pending_status, :only => :update
37
+ before_validation :get_name_and_email_from_user, :on => :create
38
+ before_validation :update_to_pending_status, :on => :update
35
39
 
36
- # copy temp options over into custom_info column
40
+ # copy temp options over into info column
37
41
  before_create :transform_options_column
38
42
 
39
43
  # denormalize campaign code and offer name columns
@@ -42,7 +46,7 @@ class Deal < ActiveRecord::Base
42
46
 
43
47
  # If a lead with no user, find the user by email or create it, then if mailing_lists
44
48
  # were passed, assign the user those mailing lists
45
- after_create :find_or_create_user, :assign_user_mailing_lists
49
+ after_create :find_or_create_user, :assign_user_mailing_lists, :add_user_contact
46
50
 
47
51
  # money column definitions for pseudo attributes (added on the reports scope)
48
52
  %w(total_value average_value total_cost average_cost).each do |money_column|
@@ -167,14 +171,6 @@ class Deal < ActiveRecord::Base
167
171
  scope :owner, lambda {|owner| where(:contact_id => owner.to_param) }
168
172
  scope :status, lambda {|status| where(:status => status) }
169
173
 
170
- def custom_info
171
- read_attribute(:options)
172
- end
173
-
174
- def custom_info=(v)
175
- write_attribute(:options, v)
176
- end
177
-
178
174
  protected
179
175
 
180
176
  def write_options(obj={})
@@ -215,13 +211,7 @@ class Deal < ActiveRecord::Base
215
211
  end
216
212
 
217
213
  def transform_options_column
218
- # NOTE this column can't be passed, it is only generated from
219
- # the custom options pseudo column
220
- self.custom_info = begin
221
- options.to_hash.map do |k, v|
222
- "%s:\n%s\n\n" % [k.to_s.titleize, v]
223
- end.join
224
- end
214
+ self.info = options.to_hash.map {|k, v| "%s:\n%s\n\n" % [k.to_s.titleize, v] }.join
225
215
  end
226
216
 
227
217
  def ensure_denormalized_columns
@@ -244,12 +234,14 @@ class Deal < ActiveRecord::Base
244
234
  if lead? && user.blank? && lead_email
245
235
  u = User.find_by_email(lead_email) || create_prospect
246
236
  update_attribute(:user_id, u.id)
247
-
248
- u.create_contact_if_missing!
249
- self.contacts << u.contact
250
237
  end
251
238
  end
252
239
 
240
+ def add_user_contact
241
+ user.create_contact_if_missing!
242
+ self.contacts << user.contact
243
+ end
244
+
253
245
  def assign_user_mailing_lists
254
246
  if @mailing_list_ids
255
247
  user.mailing_list_ids |= @mailing_list_ids
@@ -8,4 +8,15 @@ class EmailTemplate < Email
8
8
  validates :text_body, :presence => true
9
9
  validates :subject, :presence => true
10
10
  validates :from_email, :presence => true, :email => { :allow_blank => true }
11
+
12
+ def as_json(options = {})
13
+ {}.tap do |hash|
14
+ hash[:to] = recipient.email
15
+ hash[:reply_to] = reply_email
16
+ hash[:from] = from_email
17
+ hash[:subject] = render(:subject)
18
+ hash[:html_body] = render(:html_body)
19
+ hash[:text_body] = render(:text_body)
20
+ end
21
+ end
11
22
  end
@@ -13,6 +13,6 @@
13
13
  - record.users.each do |user|
14
14
  .contact-email
15
15
  -#= '* ' if contact_user_subscribed_to_newsletter?(user)
16
- = link_to(user.email, "mailto:#{user.email}")
16
+ = link_to(user.email, "mailto:#{user.email}", 'data-contact-id' => record.id, 'data-user-id' => user.id, :class => 'contact-mailto')
17
17
  = "(#{user.options.type})" if user.options.type
18
18
  = "(primary)" if user.primary?
@@ -3,8 +3,8 @@
3
3
  %label= Contact.human_attribute_name(:users)
4
4
  - resource.users.each do |user|
5
5
  .contact-email
6
- = '* ' if contact_user_subscribed_to_newsletter?(user)
7
- = link_to(user.email, "mailto:#{user.email}")
6
+ -#= '* ' if contact_user_subscribed_to_newsletter?(user)
7
+ = link_to(user.email, "mailto:#{user.email}", 'data-contact-id' => resource.id, 'data-user-id' => user.id, :class => 'contact-mailto')
8
8
  = "(#{user.options.type})" if user.options.type
9
9
  = "(primary)" if user.primary?
10
10
 
@@ -35,4 +35,3 @@
35
35
  .address= address_attribute.to_html
36
36
  .actions
37
37
  = google_maps_link(address_attribute)
38
-
@@ -14,15 +14,50 @@
14
14
  .contact-info
15
15
  %label #{Contact.human_attribute_name(:info)}:
16
16
  = contact_simple_format(resource.info.presence || t(:none))
17
+
17
18
  - if company = resource.company
18
19
  .contact-company
19
- %label= company.name
20
-
21
- .contact-company-links.actions
22
- = google_search_link(company.name)
23
- = google_news_link(company.name)
24
-
20
+ %h2
21
+ %span.contact-subheader= company.name
22
+ %span.contact-actions
23
+ = google_search_link(company.name)
24
+ = google_news_link(company.name)
25
25
  - if company.info.present?
26
26
  = k(company.info)
27
+
28
+ .contact-deals
29
+ %h2
30
+ %span.contact-subheader Deals
31
+ %span.contact-actions
32
+ = link_to_new_resource(Deal, :deal => { :contact_ids => [resource.id] })
33
+
34
+ %ul
35
+ - if (deals = resource.associated_deals.leads(false).limit(5)).blank?
36
+ %li= resource_humanize(:no_deals)
37
+ - else
38
+ - deals.each do |deal|
39
+ %li
40
+ .contact-deal-status
41
+ = deal.status
42
+ .contact-deal-name
43
+ = link_to deal.name, edit_deal_path(deal)
44
+ .contact-deal-value
45
+ = deal.value
46
+
47
+ .contact.leads
48
+ %h2
49
+ %span.contact-subheader Leads
50
+
51
+ %ul
52
+ - if (leads = resource.associated_deals.leads.limit(5)).blank?
53
+ %li= resource_humanize(:no_leads)
54
+ - else
55
+ - leads.each do |lead|
56
+ %li
57
+ .contact-lead-created-at
58
+ = l(lead.created_at)
59
+ .contact-lead-name
60
+ = link_to lead.offer_name, edit_deal_path(lead)
61
+
27
62
  .contact-sidebar
28
63
  = render 'sidebar'
@@ -11,12 +11,12 @@
11
11
  .field.select
12
12
  = f.label :category
13
13
  = f.select :category, MenuOption.options_for('Deal Category'), :include_blank => 'No Category'
14
+ .field
15
+ = f.label :campaign, :for => 'deal_campaign_select'
16
+ = f.collection_select :campaign_id, Campaign.ordered.all, :id, :to_s, {:prompt => true}, :id => 'deal_campaign_select'
14
17
  .field
15
18
  = f.label :info
16
19
  = f.text_area :info
17
- .field
18
- = f.label :custom_info
19
- = f.text_area :custom_info
20
20
  .field
21
21
  = f.label :value, nil, :class => :req
22
22
  = f.text_field :value
@@ -0,0 +1,6 @@
1
+ = title e9_t(:select_title)
2
+ = form_tag email_templates_path, :id => "email_templates_select", :method => :get do
3
+ = hidden_field_tag "contact_id", params[:contact_id]
4
+ = hidden_field_tag "user_id", params[:user_id]
5
+ = select_tag "id", options_for_select(collection.map {|e| [e.name, e.id] })
6
+ = submit_tag e9_t(:select_submit), :name => nil
@@ -27,6 +27,9 @@ en:
27
27
  send_email: Send Email
28
28
  deals:
29
29
  new_title: Create Deal
30
+ email_templates:
31
+ select_title: Choose a Template
32
+ select_submit: Go
30
33
  leads:
31
34
  index_title: Leads
32
35
  reports:
@@ -108,11 +108,12 @@ en:
108
108
  info: Background Information
109
109
  not_ok_to_email: "This contact will not receive bulk email."
110
110
  tag_instructions: "Tags are words or phrases that describe or categorize this contact.\n\nWhen searching a particular tag, the system will show all contacts with that tag."
111
+ no_deals: There are no deals associated with this contact.
112
+ no_leads: There are no leads associated with this contact.
111
113
  company:
112
114
  info: Background Information
113
115
  deal:
114
116
  info: Details
115
- custom_info: Lead Information
116
117
  lead_email: Email
117
118
  lead_name: First Name
118
119
  campaign_code: Code
data/config/routes.rb CHANGED
@@ -24,7 +24,9 @@ Rails.application.routes.draw do
24
24
 
25
25
  # contact_emails are generated by email templates, and end up in the sent emails list
26
26
  resources :contact_emails, :except => [:index, :show]
27
- resources :email_templates, :except => :show
27
+ resources :email_templates do
28
+ collection { get :select }
29
+ end
28
30
 
29
31
  resources :menu_options, :except => [:show] do
30
32
  collection { post :update_order }
data/lib/e9_crm/email.rb CHANGED
@@ -3,9 +3,11 @@ module E9Crm
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
+ attr_accessor :contact
7
+
6
8
  def locals_with_contact
7
9
  default_locals.merge({
8
- :contact => recipient.try(:contact)
10
+ :contact => @contact || recipient.try(:contact)
9
11
  })
10
12
  end
11
13
 
@@ -1,3 +1,3 @@
1
1
  module E9Crm
2
- VERSION = '0.1.12'
2
+ VERSION = '0.1.13'
3
3
  end
@@ -219,4 +219,83 @@
219
219
  $.submit_with_query();
220
220
  });
221
221
 
222
+
223
+
224
+ /*
225
+ * contact mailto links have template functionality, passing the contact and user (login)
226
+ * for the email to the email templates form, which offers the available templates and
227
+ * on submit, will return the template in its rendered form for population of a mailto.
228
+ *
229
+ * See below handling of the form.
230
+ */
231
+ $('a.contact-mailto')
232
+ .click(function(e) {
233
+ e.preventDefault();
234
+ })
235
+ .each(function(i, el) {
236
+ var $el = $(el);
237
+ $el.qtip({
238
+ content: {
239
+ text: '<img src="/images/spinner.gif" />',
240
+ ajax: {
241
+ url: '/admin/crm/email_templates/select',
242
+ dataType: 'script',
243
+ data: {
244
+ contact_id: $el.attr('data-contact-id'),
245
+ user_id: $el.attr('data-user-id')
246
+ }
247
+ }
248
+ },
249
+ position: {
250
+ at: 'bottom center', // Position the tooltip above the link
251
+ my: 'top center'
252
+ },
253
+ show: {
254
+ event: 'click',
255
+ solo: true // Only show one tooltip at a time
256
+ },
257
+ hide: 'unfocus',
258
+ style: {
259
+ classes: 'ui-tooltip-wiki ui-tooltip-light ui-tooltip-shadow'
260
+ }
261
+ })
262
+ })
263
+ ;
264
+
265
+ /*
266
+ * Behavior for the email template select form.
267
+ *
268
+ * It should intercept the submit and redirect itself to email_templates#show.json
269
+ * passing along the user_id and contact_id sent with the request.
270
+ *
271
+ * On success, it should take the json returned (the rendered email template data) and
272
+ * build a mailto href which is then opened in a new window.
273
+ */
274
+ $('form#email_templates_select').live('submit', function(e) {
275
+ e.preventDefault();
276
+
277
+ var $t = $(this),
278
+ $s = $t.find('select'),
279
+ $c = $t.find('> input[type=hidden]');
280
+
281
+ $.ajax({
282
+ url: $t.attr('action') + '/' + $s.val(),
283
+ type: 'GET',
284
+ dataType: 'json',
285
+ data: $.param($c),
286
+ success: function(data, status, xhr) {
287
+ var to = data.to,
288
+ subj = escape(data.subject),
289
+ body = escape(data.text_body);
290
+
291
+ var href = 'mailto:' + to + '?subject=' + subj + '&body=' + body;
292
+
293
+ // attempt to close our tooltip
294
+ try { $t.closest('.ui-tooltip').qtip('api').hide(); } catch(e) {}
295
+
296
+ // open mailto in new window
297
+ window.open(href, '_blank');
298
+ }
299
+ });
300
+ });
222
301
  });
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.12
5
+ version: 0.1.13
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-27 00:00:00 -04:00
13
+ date: 2011-05-31 00:00:00 -04:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -270,6 +270,7 @@ files:
270
270
  - app/views/e9_crm/email_campaigns/_form_inner.html.haml
271
271
  - app/views/e9_crm/email_templates/_form_inner.html.haml
272
272
  - app/views/e9_crm/email_templates/_header.html.haml
273
+ - app/views/e9_crm/email_templates/select.html.haml
273
274
  - app/views/e9_crm/file_download_offers/_form.html.haml
274
275
  - app/views/e9_crm/file_download_offers/_form_inner.html.haml
275
276
  - app/views/e9_crm/leads/_form.html.haml