artfully_ose 1.2.0.alpha.2 → 1.2.0.beta.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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NTZhNzA2MTY4YzkyODhmMDIzZDhiZGQ4ZDhlMzE5Yjk1YWZhNDBkOA==
4
+ NzdkN2Y3NDg3MDVhMDY5NjQ4OWM1MzlhYjlmNGFkMTI1YThlM2EzYQ==
5
5
  data.tar.gz: !binary |-
6
- OGYyZThlYTUwMzBlNDM3MmNkZGE2OGRiYzM0NTAyMjc0YjA3NGJhNw==
6
+ NzhhODQxYjMzYTFjMDI5M2RkNzZiMzg3YzYyYTVmYTc1NjcyMjgzOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDNlMDZiMmJjNGYxNGUwYWNhOWZiZjJiYzFiMGYxYTE0MTQ0OGU1NTA2ZDgz
10
- YTIxOTYxMTg3N2UwMzQ1YjhkYTVkNTA4Mjg4NGQ4ZWRmZDc1MDQ4ODQyZmJi
11
- YmNmMTgyMmI1MWRhNzhmZWVmMzM3YTYwNDExMzU0NzBiOWMxMzA=
9
+ ZThmZDFjY2RlMWQ2MGExYzlkODYyYWE2MmQ3OWY1NDYyNjk5MDIzOTQ1MDhl
10
+ NTkzN2Q0NGJhOGJiNDE4ODBhODJkODczNzZmMWE5OTVmMGUzYzVlZDhiMzI3
11
+ ZGQ3NGI4NmJiZWUzODFiM2IzYzUzNzU5NGQ3OGE2OGNmN2YyMTg=
12
12
  data.tar.gz: !binary |-
13
- ZTRjM2Q4ZGY0YzdhYmFkYmYzNTU1MDE2Zjc1YTEzMjQ3NDRmMmZkMjRjYmEy
14
- OTIxZDA1N2M4MDMxODY3N2QzYjA2YjA1OGE3ZmM3ZWI5MzRhYWQ0MGM2ZDhh
15
- NjE4ZTAzZTZlMDRiNWYxZjMwYmU2NTY0NGU2ZGIwZDM1MjQ1NTA=
13
+ MzU1N2U3MzgzODQ2MzZmOTc2MjU4YjBjYzc2MzBjMzQ0YWU2NDExMjcxZTNk
14
+ ZmJjNzBjM2JiMzVkMDUxNzM5MzZlZjBjZGI2NTZjZmVhYjEyNDdiMDkzNWVm
15
+ NWUzNTE4MWUxZTk0NzgwMGFhYzRjNGE2OGFmODRkMjU1ZGNkNGI=
@@ -119,8 +119,14 @@ ul#memberships
119
119
  #text
120
120
  padding-top: 50px
121
121
 
122
- #add-a-pass-type
122
+ #no-pass-sales
123
123
  text-align: center
124
+ width: 100%
125
+ height: 100px
126
+ #text
127
+ padding-top: 25px
128
+
129
+ #add-a-pass-type
124
130
  width: 75%
125
131
  height: 150px
126
132
  #text
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Include this method on objects that can be used to create items using Item.for
3
- # Ticket class doesn't meed it because it defines its own sold_price method
3
+ # Ticket class doesn't need it because it defines its own sold_price method
4
4
  #
5
5
  module Itemable
6
6
  extend ActiveSupport::Concern
@@ -17,17 +17,7 @@ class PdfGeneration
17
17
  end
18
18
 
19
19
  def generate
20
- response = http_party.post(PDF_POST_URL, pdf_attributes)
21
- json = JSON.parse(response.body)
22
-
23
- while !json["complete"]
24
- response = http_party.get(status_url(json))
25
- json = JSON.parse(response.body)
26
-
27
- sleeper.sleep 1 unless json["complete"]
28
- end
29
-
30
- download_url(json)
20
+ Wisepdf::Writer.new.to_pdf(content)
31
21
  end
32
22
 
33
23
  def content
@@ -0,0 +1,20 @@
1
+ module SearchByDates
2
+ attr_reader :start, :stop
3
+ attr_reader :organization
4
+
5
+ def start_with(start)
6
+ start.present? ? DateTime.parse(start) : default_start
7
+ end
8
+
9
+ def stop_with(stop)
10
+ stop.present? ? Sundial.midnightish(@organization, stop) : default_stop
11
+ end
12
+
13
+ def default_start
14
+ DateTime.now.in_time_zone(@organization.time_zone).beginning_of_month
15
+ end
16
+
17
+ def default_stop
18
+ DateTime.now.in_time_zone(@organization.time_zone).end_of_day
19
+ end
20
+ end
@@ -4,9 +4,8 @@ class MemberCardsController < ArtfullyOseController
4
4
  before_filter :find_members
5
5
 
6
6
  def new
7
- generator = MemberCardGenerator::BlanksUsaIdc6.new(@members)
8
- generator.generate
9
- redirect_to generator.download_url
7
+ pdf = MemberCardGenerator::BlanksUsaIdc6.new(@members).generate
8
+ send_data pdf, :filename => "member_cards.pdf", :type => "application/pdf", :disposition => "attachment"
10
9
  end
11
10
 
12
11
  private
@@ -89,6 +89,48 @@ class OrdersController < ArtfullyOseController
89
89
  end
90
90
  end
91
91
 
92
+ def passes
93
+ authorize! :view, Order
94
+
95
+ @organization = current_organization
96
+ @pass_type = PassType.find_by_id(params[:pass_type_id]) if params[:pass_type_id].present?
97
+ @pass_types = @organization.pass_types
98
+
99
+ request.format = :csv if params[:commit] == "Download"
100
+
101
+ search_terms = {
102
+ :start => params[:start],
103
+ :stop => params[:stop],
104
+ :organization => @organization,
105
+ :pass_type => @pass_type
106
+ }
107
+
108
+ @search = PassSaleSearch.new(search_terms) do |results|
109
+ results.paginate(:page => params[:page], :per_page => 25)
110
+ end
111
+
112
+ respond_to do |format|
113
+ format.html
114
+ format.csv do
115
+ filename = "Artfully-Pass-Sales-Export-#{DateTime.now.strftime("%m-%d-%y")}.csv"
116
+ items = ItemView.where(:product_type => "Pass")
117
+ .where('items_view.created_at > ? ', @search.start)
118
+ .where('items_view.created_at < ?', @search.stop)
119
+ .where('items_view.organization_id = ?', current_organization)
120
+ .order('items_view.created_at desc')
121
+ if @pass_type
122
+ items = items.joins(:item).
123
+ joins("INNER JOIN passes ON (items.product_type = #{::Item.sanitize('Pass')} AND items.product_id = passes.id)").
124
+ joins("INNER JOIN pass_types ON (pass_types.id = passes.pass_type_id)").
125
+ where(:pass_types => {:id => @pass_type}).
126
+ group('items_view.order_id')
127
+ end
128
+ csv_string = items.all.to_comma(:pass_sale)
129
+ send_data csv_string, :filename => filename, :type => "text/csv", :disposition => "attachment"
130
+ end
131
+ end
132
+ end
133
+
92
134
  def sales
93
135
  authorize! :view, Order
94
136
 
@@ -1,5 +1,25 @@
1
1
  class PassesReportsController < ArtfullyOseController
2
2
  def index
3
- @pass_types = current_organization.pass_types.all
3
+ @start_date = params[:start_date]
4
+ @end_date = params[:end_date]
5
+ @organization = current_user.current_organization
6
+ @pass_types = @organization.pass_types
7
+ @pass_type = PassType.find(params[:pass_type]) if params[:pass_type].present?
8
+ @report = nil
9
+ @report = PassesReport.new(@organization, @pass_type, @start_date, @end_date)
10
+ @rows = @report.rows.paginate(:page => params[:page], :per_page => 100) unless @report.nil?
11
+
12
+ @options_for_select = @pass_types.collect{ |p| [p.name, p.id] }
13
+ @options_for_select.unshift [PassType::ALL_PASSES_STRING, nil]
14
+
15
+ respond_to do |format|
16
+ format.html
17
+
18
+ format.csv do
19
+ @filename = [ @report.header, ".csv" ].join
20
+ @csv_string = @report.rows.to_comma
21
+ send_data @csv_string, :filename => @filename, :type => "text/csv", :disposition => "attachment"
22
+ end
23
+ end
4
24
  end
5
25
  end
@@ -252,6 +252,16 @@ module ArtfullyOseHelper
252
252
  select_tag membership_type_id, raw(options), :class => "span3"
253
253
  end
254
254
 
255
+ def select_pass_type_for_sales_search(pass_types, pass_type_id, default)
256
+ options =
257
+ [
258
+ content_tag(:option, " --- All Pass Types --- ", :value => ""),
259
+ options_from_collection_for_select(pass_types, :id, :name, default)
260
+ ].join
261
+
262
+ select_tag pass_type_id, raw(options), :class => "span3"
263
+ end
264
+
255
265
  def nav_dropdown(text, link='#')
256
266
  link_to ERB::Util.html_escape(text) + ' <b class="caret"></b>'.html_safe, link, :class => 'dropdown-toggle', 'data-toggle' => 'dropdown'
257
267
  end
@@ -145,4 +145,45 @@ class ItemView < ActiveRecord::Base
145
145
  person("Do Not Email") { |person| person.do_not_email }
146
146
  person("Do Not Call") { |person| person.do_not_call }
147
147
  end
148
+
149
+ comma :pass_sale do
150
+ created_at_local_to_organization("Date of Purchase")
151
+
152
+ item('Pass Type') { |item| item.product.pass_type.name }
153
+ payment_method("Payment Method")
154
+ price("Price") { |cents| number_to_currency(cents.to_f/100) }
155
+ special_instructions("Special Instructions")
156
+ notes("Notes")
157
+
158
+ person("Email") { |person| person.email }
159
+ person("Salutation") { |person| person.salutation }
160
+ person("First Name") { |person| person.first_name }
161
+ person("Middle Name") { |person| person.middle_name }
162
+ person("Last Name") { |person| person.last_name }
163
+ person("Suffix") { |person| person.suffix }
164
+ person("Title") { |person| person.title }
165
+ person("Type") { |person| person.type }
166
+ person("Subtype") { |person| person.subtype }
167
+ person("Company Name") { |person| person.company_name }
168
+
169
+ person("Address 1") { |person| person.address && person.address.address1 }
170
+ person("Address 2") { |person| person.address && person.address.address2 }
171
+ person("City") { |person| person.address && person.address.city }
172
+ person("State") { |person| person.address && person.address.state }
173
+ person("Zip") { |person| person.address && person.address.zip }
174
+ person("Country") { |person| person.address && person.address.country }
175
+ person("Phone1 type") { |person| person.phones[0] && person.phones[0].kind }
176
+ person("Phone1 number") { |person| person.phones[0] && person.phones[0].number }
177
+ person("Phone2 type") { |person| person.phones[1] && person.phones[1].kind }
178
+ person("Phone2 number") { |person| person.phones[1] && person.phones[1].number }
179
+ person("Phone3 type") { |person| person.phones[2] && person.phones[2].kind }
180
+ person("Phone3 number") { |person| person.phones[2] && person.phones[2].number }
181
+ person("Website") { |person| person.website }
182
+ person("Twitter Handle") { |person| person.twitter_handle }
183
+ person("Facebook URL") { |person| person.facebook_url }
184
+ person("Linked In Url") { |person| person.linked_in_url }
185
+ person("Tags") { |person| person.tags.join("|") }
186
+ person("Do Not Email") { |person| person.do_not_email }
187
+ person("Do Not Call") { |person| person.do_not_call }
188
+ end
148
189
  end
@@ -3,6 +3,10 @@ module Ext
3
3
  module Member
4
4
  end
5
5
 
6
+ module ServiceFee
7
+ SERVICE_FEE = 0
8
+ end
9
+
6
10
  module User
7
11
  def need_more_info?
8
12
  false
@@ -1,5 +1,51 @@
1
1
  class FeeStrategy
2
+ def ticket_fee
3
+ ARTFULLY_CONFIG[:ticket_fee] || 0
4
+ end
5
+
2
6
  def apply_to_cart(cart)
3
- #noop
7
+ return if cart.is_a? BoxOffice::Cart
8
+
9
+ handle_tickets(cart)
10
+ handle_memberships(cart)
11
+ handle_passes(cart)
12
+ end
13
+
14
+ def handle_tickets(cart)
15
+ cart.tickets.each do |ticket|
16
+ if ticket.price > 0
17
+ ticket.service_fee = ticket_fee
18
+ elsif ticket.price == 0
19
+ ticket.service_fee = 0
20
+ end
21
+
22
+ if ticket.cart_price == 0 && waive_fee_for?(ticket)
23
+ ticket.service_fee = 0
24
+ end
25
+
26
+ ticket.save
27
+ end
28
+ end
29
+
30
+ def handle_memberships(cart)
31
+ cart.memberships.each do |membership|
32
+ membership.service_fee = membership.membership_type.hide_fee? ? 0 : (membership.cart_price || membership.price) * MembershipType::SERVICE_FEE
33
+ membership.save
34
+ end
35
+ end
36
+
37
+ def handle_passes(cart)
38
+ cart.passes.each do |pass|
39
+ pass.service_fee = pass.pass_type.hide_fee? ? 0 : pass.price * PassType::SERVICE_FEE
40
+ pass.save
41
+ end
42
+ end
43
+
44
+ def waive_fee_for?(ticket)
45
+ #
46
+ # This match is too tightly coupled to discount. Also, horrible.
47
+ # Check needs to be made because cart_price == 0 && BOGO means fee is applied
48
+ #
49
+ (ticket.discount.try(:promotion_type) == "DollarsOffTickets") || ticket.pass.present?
4
50
  end
5
51
  end
data/app/models/item.rb CHANGED
@@ -22,9 +22,10 @@ class Item < ActiveRecord::Base
22
22
  validates_presence_of :product_type, :price, :realized_price, :net
23
23
  validates_inclusion_of :product_type, :in => %( Ticket Donation Membership Pass)
24
24
 
25
- scope :donation, where(:product_type => 'Donation')
26
- scope :membership, where(:product_type => 'Membership')
27
- scope :ticket, where(:product_type => 'Ticket')
25
+ scope :donation, where(:product_type => 'Donation')
26
+ scope :membership, where(:product_type => 'Membership')
27
+ scope :ticket, where(:product_type => 'Ticket')
28
+ scope :sold_or_comped, where("state in ('purchased','comped','settled')")
28
29
 
29
30
  scope :imported, joins(:order).merge(Order.imported)
30
31
  scope :not_imported, joins(:order).merge(Order.not_imported)
@@ -85,9 +85,10 @@ class OrderProcessor < Struct.new(:order, :options)
85
85
  end
86
86
 
87
87
  def generate_pdf
88
- pdf_generator = PdfGeneration.new(order)
89
- download_url = pdf_generator.generate
90
- order.pdf = URI.parse(download_url)
88
+ pdf = PdfGeneration.new(order).generate
89
+ file = Tempfile.new(["#{order.id}", '.pdf'])
90
+ file << pdf.force_encoding("UTF-8")
91
+ order.pdf = file
91
92
  order.save
92
93
  end
93
94
  end
data/app/models/member.rb CHANGED
@@ -169,9 +169,10 @@ class Member < ActiveRecord::Base
169
169
  end
170
170
 
171
171
  def generate_pdf
172
- pdf_generator = PdfGeneration.new(self)
173
- download_url = pdf_generator.generate
174
- self.pdf = URI.parse(download_url)
172
+ pdf = PdfGeneration.new(self).generate
173
+ file = Tempfile.new(["#{self.id}", '.pdf'])
174
+ file << pdf.force_encoding("UTF-8")
175
+ self.pdf = file
175
176
  self.save
176
177
  end
177
178
 
@@ -1,7 +1,7 @@
1
1
  class MembershipSaleSearch
2
+ include SearchByDates
2
3
 
3
- attr_reader :start, :stop
4
- attr_reader :organization, :membership_type
4
+ attr_reader :membership_type
5
5
 
6
6
  def initialize(terms)
7
7
  @organization = terms[:organization]
@@ -15,22 +15,4 @@ class MembershipSaleSearch
15
15
  def results
16
16
  @results ||= Order.membership_sale_search(self).select(&:has_membership?)
17
17
  end
18
-
19
- private
20
-
21
- def start_with(start)
22
- start.present? ? DateTime.parse(start) : default_start
23
- end
24
-
25
- def stop_with(stop)
26
- stop.present? ? Sundial.midnightish(@organization, stop) : default_stop
27
- end
28
-
29
- def default_start
30
- DateTime.now.in_time_zone(@organization.time_zone).beginning_of_month
31
- end
32
-
33
- def default_stop
34
- DateTime.now.in_time_zone(@organization.time_zone).end_of_day
35
- end
36
18
  end
@@ -1,4 +1,5 @@
1
1
  class MembershipType < ActiveRecord::Base
2
+ include Ext::Integrations::ServiceFee
2
3
  extend ::ArtfullyOseHelper
3
4
  attr_accessible :name, :price, :fee, :number_of_shows,
4
5
  :plan, :on_sale, :description, :ends_at,
@@ -20,8 +21,6 @@ class MembershipType < ActiveRecord::Base
20
21
  scope :sales_valid, where("sales_start_at < ? or sales_start_at is null", DateTime.now).where("sales_end_at > ? or sales_end_at is null", DateTime.now)
21
22
  scope :not_ended, where('ends_at > ?', DateTime.now)
22
23
 
23
- SERVICE_FEE = 0.05
24
-
25
24
  comma do
26
25
  name
27
26
  description
data/app/models/order.rb CHANGED
@@ -121,6 +121,10 @@ class Order < ActiveRecord::Base
121
121
  def discount_codes
122
122
  tickets.collect(&:discount).uniq.compact
123
123
  end
124
+
125
+ def pass_codes
126
+ tickets.collect(&:pass).uniq.compact
127
+ end
124
128
 
125
129
  def imported?
126
130
  !self.import_id.nil?
@@ -269,6 +273,23 @@ class Order < ActiveRecord::Base
269
273
  standard = standard.where("membership_types.id = ?", search.membership_type.id) if search.membership_type if search.membership_type
270
274
  standard.all
271
275
  end
276
+
277
+ def self.pass_sale_search(search)
278
+ standard = ::Order.
279
+ where("orders.type != ?", ::Reseller::Order.name).
280
+ joins(:items).
281
+ joins("INNER JOIN passes ON (items.product_type = #{::Item.sanitize('Pass')} AND items.product_id = passes.id)").
282
+ joins("INNER JOIN pass_types ON (pass_types.id = passes.pass_type_id)").
283
+ includes([:organization, :person]).
284
+ group('orders.id')
285
+
286
+
287
+ standard = standard.after(search.start) if search.start
288
+ standard = standard.before(search.stop) if search.stop
289
+ standard = standard.where('orders.organization_id = ?', search.organization.id) if search.organization
290
+ standard = standard.where("pass_types.id = ?", search.pass_type.id) if search.pass_type if search.pass_type
291
+ standard.all
292
+ end
272
293
 
273
294
  def has_single_donation?
274
295
  (donations.size == 1) && tickets.empty?
@@ -307,6 +328,14 @@ class Order < ActiveRecord::Base
307
328
  items.select(&:membership?).present?
308
329
  end
309
330
 
331
+ def has_pass?
332
+ items.select(&:pass?).present?
333
+ end
334
+
335
+ def items_that_used_pass
336
+ items.select{ |i| i.pass_id.present? }
337
+ end
338
+
310
339
  def sum_donations
311
340
  all_donations.collect{|item| item.total_price.to_i}.sum
312
341
  end
data/app/models/pass.rb CHANGED
@@ -18,6 +18,7 @@ class Pass < ActiveRecord::Base
18
18
 
19
19
  scope :not_expired, lambda { |time = DateTime.now| where("#{Pass.table_name}.ends_at > ?", time) }
20
20
  scope :expired, lambda { |time = DateTime.now| where("#{Pass.table_name}.ends_at < ?", time) }
21
+ scope :owned, where('person_id is not null')
21
22
 
22
23
  def self.for(pass_type)
23
24
  new.tap do |pass|
@@ -30,7 +31,7 @@ class Pass < ActiveRecord::Base
30
31
  pass.starts_at = pass_type.starts_at
31
32
  pass.ends_at = pass_type.ends_at
32
33
  end
33
- end
34
+ end
34
35
 
35
36
  def adjust_ends_at
36
37
  self.ends_at = self.ends_at.end_of_day unless self.ends_at.blank?
@@ -0,0 +1,18 @@
1
+ class PassSaleSearch
2
+ include SearchByDates
3
+
4
+ attr_reader :pass_type
5
+
6
+ def initialize(terms)
7
+ @organization = terms[:organization]
8
+ @pass_type = terms[:pass_type]
9
+ @start = start_with(terms[:start])
10
+ @stop = stop_with(terms[:stop])
11
+
12
+ @results = yield(results) if block_given?
13
+ end
14
+
15
+ def results
16
+ @results ||= Order.pass_sale_search(self).select(&:has_pass?)
17
+ end
18
+ end
@@ -1,4 +1,5 @@
1
1
  class PassType < ActiveRecord::Base
2
+ include Ext::Integrations::ServiceFee
2
3
  include OhNoes::Destroy
3
4
  extend ::ArtfullyOseHelper
4
5
 
@@ -14,7 +15,7 @@ class PassType < ActiveRecord::Base
14
15
  scope :on_sale, where(:on_sale => true)
15
16
  scope :not_ended, where('ends_at > ?', DateTime.now)
16
17
 
17
- SERVICE_FEE = 0.05
18
+ ALL_PASSES_STRING = "ALL PASSES"
18
19
 
19
20
  comma do
20
21
  name
@@ -30,6 +31,10 @@ class PassType < ActiveRecord::Base
30
31
  sales_start_at
31
32
  sales_end_at
32
33
  end
34
+
35
+ def sold
36
+ self.passes.select{ |p| p.person_id.present? }
37
+ end
33
38
 
34
39
  def passerize
35
40
  self.name.end_with?("Pass") ? self.name : self.name + " Pass"
@@ -0,0 +1,117 @@
1
+ class PassesReport
2
+ attr_accessor :pass_type, :header, :start_date, :end_date, :rows, :counts
3
+ attr_accessor :tickets_sold, :passes_sold, :total_tickets, :tickets_remaining, :original_price, :discounted
4
+ extend ::ArtfullyOseHelper
5
+
6
+ def pass_type_name
7
+ pass_type.try(:name) || PassType::ALL_PASSES_STRING
8
+ end
9
+
10
+ def initialize(organization, pass_type, start_date, end_date)
11
+ self.pass_type = pass_type
12
+ self.start_date = start_date
13
+ self.end_date = end_date
14
+
15
+ @orders = find_orders
16
+
17
+ self.rows = []
18
+ @orders.each do |order|
19
+ self.rows << Row.new(order)
20
+ end
21
+
22
+ build_header
23
+
24
+ self.passes_sold = count_passes_sold
25
+ self.total_tickets = calculate_total_tickets
26
+ self.tickets_sold = self.rows.inject(0) { |total, row| total + row.ticket_count}
27
+ self.tickets_remaining= self.total_tickets - self.tickets_sold
28
+ self.original_price = self.rows.inject(0) { |total, row| total + row.original_price }
29
+ self.discounted = self.rows.inject(0) { |total, row| total + row.discounted }
30
+ end
31
+
32
+ def calculate_total_tickets
33
+ @passes = Pass.owned
34
+ if self.pass_type.present?
35
+ @passes = @passes.where(:pass_type_id => self.pass_type.id)
36
+ end
37
+
38
+ @passes.sum(:tickets_allowed)
39
+ end
40
+
41
+ def count_passes_sold
42
+ @items = Item.sold_or_comped.where(:product_type => "Pass")
43
+ @items = @items.joins("INNER join passes ON items.product_id = passes.id")
44
+ @items = @items.joins("INNER join pass_types ON passes.pass_type_id = pass_types.id")
45
+
46
+ if self.pass_type.present?
47
+ @items = @items.where("pass_types.id" => self.pass_type)
48
+ end
49
+
50
+ @items = @items.joins(:order)
51
+ @items = @items.where('orders.created_at > ?',self.start_date) unless start_date.blank?
52
+ @items = @items.where('orders.created_at < ?',self.end_date) unless end_date.blank?
53
+ @items.count
54
+ end
55
+
56
+ def find_orders
57
+ @orders = Order.includes(:person, :items => [:show => :event])
58
+ .joins(:items)
59
+ .joins("INNER join passes ON items.pass_id = passes.id")
60
+ .joins("INNER join pass_types ON passes.pass_type_id = pass_types.id")
61
+ .group('orders.id')
62
+ .order('orders.created_at desc')
63
+
64
+ if pass_type.nil?
65
+ @orders = @orders.where("pass_types.id is not null")
66
+ else
67
+ @orders = @orders.where("pass_types.id" => self.pass_type.id)
68
+ end
69
+
70
+ @orders = @orders.where('orders.created_at > ?',self.start_date) unless start_date.blank?
71
+ @orders = @orders.where('orders.created_at < ?',self.end_date) unless end_date.blank?
72
+ @orders
73
+ end
74
+
75
+ def build_header
76
+ self.header = pass_type_name
77
+ if self.start_date.blank? && self.end_date.blank?
78
+ return
79
+ elsif self.start_date.blank?
80
+ self.header = self.header + " through #{I18n.localize(DateTime.parse(self.end_date), :format => :slashed_date)}"
81
+ elsif self.end_date.blank?
82
+ self.header = self.header + " since #{I18n.localize(DateTime.parse(self.start_date), :format => :slashed_date)}"
83
+ else
84
+ self.header = self.header + " from #{I18n.localize(DateTime.parse(self.start_date), :format => :slashed_date)} through #{I18n.localize(DateTime.parse(self.end_date), :format => :slashed_date)}"
85
+ end
86
+ end
87
+
88
+ class Row
89
+ attr_accessor :order, :show, :pass_type, :ticket_count, :original_price, :gross, :discounted
90
+
91
+ def initialize(order)
92
+ self.order = order
93
+ self.pass_type = order.pass_codes.first.pass_type.name
94
+ self.show = order.items_that_used_pass.first.show
95
+ self.original_price = order.items_that_used_pass.inject(0) { |total, item| total + item.original_price }
96
+ self.gross = order.items_that_used_pass.inject(0) { |total, item| total + item.price }
97
+ self.discounted = self.original_price - self.gross
98
+ self.ticket_count = order.items_that_used_pass.length
99
+ self.ticket_count = self.ticket_count * -1 if !order.items_that_used_pass.select(&:refund?).empty?
100
+ end
101
+
102
+ comma do
103
+ pass_type("Pass")
104
+ order("Order") { |order| order.id }
105
+ order("Order Date") { |order| order.created_at }
106
+ order("First Name") { |order| order.person.first_name }
107
+ order("Last Name") { |order| order.person.last_name }
108
+ order("Email") { |order| order.person.email }
109
+ show("Event") { |show| show.event.name }
110
+ ticket_count
111
+ original_price { |original_price| DiscountsReport.number_as_cents original_price }
112
+ discounted { |discounted| DiscountsReport.number_as_cents discounted }
113
+ gross { |gross| DiscountsReport.number_as_cents gross }
114
+ end
115
+
116
+ end
117
+ end
@@ -1,7 +1,7 @@
1
1
  class SaleSearch
2
-
3
- attr_reader :start, :stop
4
- attr_reader :organization, :event, :show
2
+ include SearchByDates
3
+
4
+ attr_reader :event, :show
5
5
 
6
6
  def initialize(terms)
7
7
  @organization = terms[:organization]
@@ -16,22 +16,4 @@ class SaleSearch
16
16
  def results
17
17
  @results ||= Order.sale_search(self).select(&:has_ticket?)
18
18
  end
19
-
20
- private
21
-
22
- def start_with(start)
23
- start.present? ? DateTime.parse(start) : default_start
24
- end
25
-
26
- def stop_with(stop)
27
- stop.present? ? Sundial.midnightish(@organization, stop) : default_stop
28
- end
29
-
30
- def default_start
31
- DateTime.now.in_time_zone(@organization.time_zone).beginning_of_month
32
- end
33
-
34
- def default_stop
35
- DateTime.now.in_time_zone(@organization.time_zone).end_of_day
36
- end
37
19
  end
@@ -6,9 +6,9 @@
6
6
  = form_tag contributions_path, :method => :get, :class => 'well form-inline' do
7
7
  .pull-left
8
8
  = label_tag :start, "From", :class => "control-label"
9
- = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :readonly => true, :class => 'datepicker input-small'
9
+ = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :class => 'datepicker input-small'
10
10
  = label_tag :stop, "To", :class => "control-label"
11
- = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :readonly => true, :class => 'datepicker input-small'
11
+ = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :class => 'datepicker input-small'
12
12
  .pull-right
13
13
  = submit_tag "Search", :class => "btn"
14
14
  = submit_tag "Download", :class => "btn"
@@ -2,26 +2,16 @@
2
2
  - content_for :header do
3
3
  %h1 Membership Orders
4
4
 
5
- #print-cards.modal.hide.fade
6
- .modal-header
7
- .close{'data-dismiss'=>'modal'} x
8
- %h3 Generating Member Cards
9
- .modal-body
10
- %p
11
- A PDF is being generated and will automatically begin downloading when ready...
12
- .progress.progress-success.progress-striped.active
13
- .bar{:style => 'width: 90%'}
14
-
15
5
  #donations-search
16
6
 
17
7
  .control-group.well
18
- = form_tag membership_orders_path, :method => :get, :class => 'form-inline' do
8
+ = form_tag membership_orders_path, :method => :get, :id => 'memberships-date-search-form', :class => 'form-inline' do
19
9
  .pull-left
20
10
  = label_tag :start, "From", :class => "control-label"
21
- = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :readonly => true, :class => 'datepicker input-small'
11
+ = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :class => 'datepicker input-small'
22
12
 
23
13
  = label_tag :stop, "To", :class => "control-label"
24
- = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :readonly => true, :class => 'datepicker input-small'
14
+ = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :class => 'datepicker input-small'
25
15
 
26
16
  - if @membership_types.present?
27
17
  = label_tag :membership_type_id, nil, :class => "control-label"
@@ -41,7 +31,7 @@
41
31
  = link_to 'Export to CSV', params.merge(:format => 'csv').delete_if {|k,v| :commit == k.to_sym }
42
32
  - with_kit(:membership) do
43
33
  %li
44
- =link_to 'Print Member Cards', new_member_card_path({:start => @search.start, :stop => @search.stop, :membership_type_id => @search.membership_type.try(:id)}), :onclick => "$('#print-cards').modal();"
34
+ =link_to 'Print Member Cards', new_member_card_path({:start => @search.start, :stop => @search.stop, :membership_type_id => @search.membership_type.try(:id)})
45
35
 
46
36
 
47
37
  - if @search.results.present?
@@ -59,16 +49,22 @@
59
49
  %tbody
60
50
  - @search.results.each do |order|
61
51
  - order.items.select(&:membership?).each_with_index do |item, index|
62
- %tr
63
- - if index == 0
52
+ - if index == 0
53
+ %tr{:id => "order_#{order.id}"}
64
54
  - rowspan = order.items.select(&:membership?).size
65
55
  %td{:rowspan => rowspan}= link_to order.id, order_path(order.id)
66
56
  %td{:rowspan => rowspan}= l(order.created_at_local_to_organization, :format => :short)
67
57
  %td{:rowspan => rowspan}= link_to_person(order.person)
68
58
  %td{:rowspan => rowspan}= (order.payment_method || "")
69
- %td
70
- ="#{item.product.membership_type.name}"
71
- %td= number_as_cents item.price
59
+ %td
60
+ ="#{item.product.membership_type.name}"
61
+ %td= number_as_cents item.price
62
+ - else
63
+ %tr
64
+ %td
65
+ ="#{item.product.membership_type.name}"
66
+ %td= number_as_cents item.price
67
+
72
68
  = will_paginate(@search.results)
73
69
 
74
70
  - else
@@ -0,0 +1,97 @@
1
+ - in_section :transactions
2
+ - content_for :header do
3
+ %h1 Pass Orders
4
+
5
+ #donations-search
6
+
7
+ .control-group.well
8
+ = form_tag passes_orders_path, :method => :get, :id => 'passes-date-search-form', :class => 'form-inline' do
9
+ .pull-left
10
+ = label_tag :start, "From", :class => "control-label"
11
+ = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :class => 'datepicker input-small'
12
+
13
+ = label_tag :stop, "To", :class => "control-label"
14
+ = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :class => 'datepicker input-small'
15
+
16
+ - if @pass_types.present?
17
+ = label_tag :pass_type_id, nil, :class => "control-label"
18
+ = raw select_pass_type_for_sales_search @pass_types, :pass_type_id, params[:pass_type_id]
19
+
20
+ = submit_tag "Search", :class => "btn"
21
+
22
+
23
+ - if @search.results.present?
24
+ .pull-right
25
+
26
+ %ul.in-table
27
+ %li.dropdown#bulkactions
28
+ =icon_link_to('Work with...', "#menu#bulkactions", 'fa-asterisk', 'dropdown-toggle dropdown btn', '')
29
+ %ul.dropdown-menu
30
+ %li
31
+ = link_to 'Export to CSV', params.merge(:format => 'csv').delete_if {|k,v| :commit == k.to_sym }
32
+
33
+
34
+ - if @search.results.present?
35
+
36
+ #tickets
37
+ %table.standalone.zebra.table
38
+ %thead
39
+ %tr
40
+ %th Order
41
+ %th Time
42
+ %th Person
43
+ %th Method
44
+ %th Membership Type
45
+ %th Amount
46
+ %tbody
47
+ - @search.results.each do |order|
48
+ - order.items.select(&:pass?).each_with_index do |item, index|
49
+ - if index == 0
50
+ %tr{:id => "order_#{order.id}"}
51
+ - rowspan = order.items.select(&:pass?).size
52
+ %td{:rowspan => rowspan}= link_to order.id, order_path(order.id)
53
+ %td{:rowspan => rowspan}= l(order.created_at_local_to_organization, :format => :short)
54
+ %td{:rowspan => rowspan}= link_to_person(order.person)
55
+ %td{:rowspan => rowspan}= (order.payment_method || "")
56
+ %td
57
+ ="#{item.product.pass_type.name}"
58
+ %td= number_as_cents item.price
59
+ - else
60
+ %tr
61
+ %td
62
+ ="#{item.product.pass_type.name}"
63
+ %td= number_as_cents item.price
64
+
65
+ = will_paginate(@search.results)
66
+
67
+ - else
68
+
69
+ %h4= "No sales found."
70
+
71
+ - content_for :custom_js do
72
+ :javascript
73
+ var printCardInterval;
74
+
75
+ $('#print-cards').on('show', function (show) {
76
+ // Remove any existing intervals
77
+ clearInterval(printCardInterval);
78
+
79
+ // Initial hurry to 90%
80
+ var bar = $('#print-cards .bar');
81
+ $(bar).css('width', '1%').animate({width: '90%'}, 1500, function() {
82
+
83
+ // Progress to 100% from there
84
+ var times = 0;
85
+ printCardInterval = setInterval(function() {
86
+ // Count this run
87
+ times = times + 1;
88
+
89
+ // Update the progress bar
90
+ $(bar).css('width', (90 + times) + '%');
91
+
92
+ // Clear the interval on the last run
93
+ if (times > 9) { clearInterval(printCardInterval); }
94
+ }, 1000);
95
+
96
+ });
97
+ });
@@ -7,10 +7,10 @@
7
7
  .control-group.well
8
8
  .pull-left
9
9
  = label_tag :start, "From", :class => "control-label"
10
- = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :readonly => true, :class => 'datepicker input-small'
10
+ = text_field_tag :start,"#{l @search.start, :format => :date_for_input}", :class => 'datepicker input-small'
11
11
 
12
12
  = label_tag :stop, "To", :class => "control-label"
13
- = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :readonly => true, :class => 'datepicker input-small'
13
+ = text_field_tag :stop, "#{l @search.stop, :format => :date_for_input}", :class => 'datepicker input-small'
14
14
 
15
15
  - if @events.present?
16
16
  = label_tag :event_id, nil, :class => "control-label"
@@ -21,7 +21,7 @@
21
21
  %td=link_to pass_type.name, edit_pass_type_path(pass_type)
22
22
  %td=l pass_type.starts_at, :format => :slashed_date
23
23
  %td=l pass_type.ends_at, :format => :slashed_date
24
- %td.right=pass_type.passes.count
24
+ %td.right=pass_type.sold.count
25
25
  %td.right=pass_type.tickets_allowed
26
26
  %td.right=number_as_cents pass_type.price
27
27
  %td.right
@@ -1,2 +1,80 @@
1
1
  -content_for :header do
2
- %h1 Passes
2
+ %h1 Pass Usage
3
+
4
+ = form_tag passes_reports_path, :method => :get, :class => 'well form-inline' do
5
+ = label_tag :pass_type, "Pass", :class => "control-label"
6
+ = select_tag :pass_type, options_for_select(@options_for_select, @report.pass_type.try(:id))
7
+
8
+
9
+ = label_tag :start_date, "From", :class => "control-label"
10
+ = text_field_tag :start_date, @start_date, :readonly => true, :class => 'datepicker input-small'
11
+
12
+ = label_tag :end_date, "To", :class => "control-label"
13
+ = text_field_tag :end_date, @end_date, :readonly => true, :class => 'datepicker input-small'
14
+
15
+ .pull-right
16
+ = submit_tag "Search", :class => "btn"
17
+
18
+ - unless @report.nil?
19
+
20
+ .row-fluid.botton-room
21
+ .span9
22
+ %h3=@report.header
23
+ .span3
24
+ .pull-right
25
+ =link_to "Download CSV", passes_reports_path(code: @report.pass_type, start_date: @report.start_date, end_date: @report.end_date, format: "csv"), :class => "btn"
26
+
27
+ #statement-summary.bottom-room.well
28
+ .row-fluid
29
+ .span4
30
+ .center.headline-stat#passes-sold-stat
31
+ =@report.passes_sold
32
+ .center.headline-caption
33
+ %h6 PASSES SOLD
34
+ .span4
35
+ .center.headline-stat#tickets-sold-stat
36
+ =@report.tickets_sold
37
+ .center.headline-caption
38
+ %h6 TICKETS SOLD
39
+ .span4
40
+ .center.headline-stat#tickets-remaining-stat
41
+ =@report.tickets_remaining
42
+ .center.headline-caption
43
+ %h6 TICKETS REMAINING
44
+
45
+ .row
46
+ .span12
47
+ %table.table.zebra#order-table
48
+ %thead
49
+ %tr
50
+ %th{:style=>'width:200px'} Pass
51
+ %th Order/Date
52
+ %th{:style=>'width:200px'} Person
53
+ %th Event
54
+ %th.right Tickets
55
+ %th.right Orig. Price
56
+ %th.right Gross
57
+ %tbody
58
+ -if @rows.empty?
59
+ %tr
60
+ %td{:colspan => 7}
61
+ .no-image#no-pass-sales
62
+ #text
63
+ =icon_tag '299-ticket'
64
+ %br
65
+ This pass hasn't been used to buy any tickets.
66
+ -@rows.each do |row|
67
+ %tr
68
+ %td=row.pass_type
69
+ %td
70
+ =link_to row.order.id, order_path(row.order)
71
+ %br
72
+ =l row.order.created_at, :format => :slashed_date
73
+ %td
74
+ =link_to row.order.person, person_path(row.order.person)
75
+ %td=link_to row.show.event.name, event_path(row.show.event)
76
+ %td.right=row.ticket_count
77
+ %td.right=number_as_cents row.original_price
78
+ %td.right=number_as_cents row.gross
79
+
80
+ = will_paginate(@rows)
@@ -17,7 +17,7 @@
17
17
  .title.active
18
18
  .price
19
19
  =number_as_cents pass_type.price
20
- .membership_type_name
20
+ .pass_type_name
21
21
  =pass_type.name
22
22
  =pass_type.description
23
23
  %br
@@ -26,7 +26,7 @@
26
26
  = form_for pass_type, :as => :pass_type, :url => store_order_path(@store_organization.cached_slug), :html => {:id => "edit_pass_type_#{pass_type.id}"}, :method => :post do |f|
27
27
  = f.hidden_field :id
28
28
  = select_tag :quantity, options_for_select((1..6).to_a.map {|i| [pluralize(i, "#{pass_type.passerize}"), i]})
29
- = f.submit 'Add to cart', :class => 'btn btn-primary', :style => 'margin-top: -10px'
29
+ = f.submit 'Add to cart', :class => 'add-to-cart btn btn-primary', :style => 'margin-top: -10px'
30
30
 
31
31
  .span4
32
32
  .side-section
data/config/routes.rb CHANGED
@@ -286,6 +286,7 @@ Rails.application.routes.draw do
286
286
  resource :assignment, :only => [ :new, :create ]
287
287
  collection do
288
288
  get :membership
289
+ get :passes
289
290
  get :sales
290
291
  end
291
292
  member do
@@ -1,3 +1,3 @@
1
1
  module ArtfullyOse
2
- VERSION = "1.2.0.alpha.2"
2
+ VERSION = "1.2.0.beta.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: artfully_ose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0.alpha.2
4
+ version: 1.2.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artful.ly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-26 00:00:00.000000000 Z
11
+ date: 2014-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -1916,6 +1916,7 @@ files:
1916
1916
  - app/concerns/itemable.rb
1917
1917
  - app/concerns/oh_noes.rb
1918
1918
  - app/concerns/pdf_generation.rb
1919
+ - app/concerns/search_by_dates.rb
1919
1920
  - app/concerns/unrefundable.rb
1920
1921
  - app/controllers/actions_controller.rb
1921
1922
  - app/controllers/addresses_controller.rb
@@ -2122,8 +2123,10 @@ files:
2122
2123
  - app/models/organization_ability.rb
2123
2124
  - app/models/parsed_row.rb
2124
2125
  - app/models/pass.rb
2126
+ - app/models/pass_sale_search.rb
2125
2127
  - app/models/pass_summary.rb
2126
2128
  - app/models/pass_type.rb
2129
+ - app/models/passes_report.rb
2127
2130
  - app/models/payment.rb
2128
2131
  - app/models/payments/cash_payment.rb
2129
2132
  - app/models/payments/check_payment.rb
@@ -2354,6 +2357,7 @@ files:
2354
2357
  - app/views/orders/_search_form.haml
2355
2358
  - app/views/orders/index.html.haml
2356
2359
  - app/views/orders/membership.html.haml
2360
+ - app/views/orders/passes.html.haml
2357
2361
  - app/views/orders/sales.html.haml
2358
2362
  - app/views/orders/show.html.haml
2359
2363
  - app/views/organizations/_connection_form.html.haml