artfully_ose 1.2.0.alpha.2 → 1.2.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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