Skip to content

Instantly share code, notes, and snippets.

@JamesDullaghan
Last active August 29, 2015 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamesDullaghan/9f79e4f07b979312cbfe to your computer and use it in GitHub Desktop.
Save JamesDullaghan/9f79e4f07b979312cbfe to your computer and use it in GitHub Desktop.
class Booking::ReportGenerator < ReportGenerator
# Presenter class overriding superclass method
#
# @return [Class]
#
# @api public
def presenter_class
ReportPresenter
end
# Column titles method overriding superclass method
#
# @return [Array<String>]
#
# @api public
def column_titles
[
'Cust. Name',
'Email',
'Phone',
'Address',
'Location',
'Check in',
'Check out',
'Room',
'Pet Name',
'Breed',
'# Nights',
'Source',
'Amount'
]
end
# Stat row methods
#
# @return [Array<Symbols>]
#
# @api public
def stat_row_methods
[
:user_full_name,
:user_email,
:user_phone,
:user_full_address,
:location_address,
:display_check_in,
:display_check_out,
:room_names,
:pets_names,
:pets_breeds,
:nights,
:source,
:total_price_with_discount
]
end
# Temporary path for report generation
#
# @return [String]
#
# @api public
def file_path
file_name = business_entity.name.parameterize.underscore
"#{Rails.root}/tmp/#{file_name}_#{date.year}_report.xls"
end
end
# Booking Reports for internal use
class Internal::ReportGenerator < ReportGenerator
# Internal Report Presenter
#
# @return [Class]
#
# @api public
def presenter_class
InternalReportPresenter
end
# Column titles for internal booking report
#
# @return [Array<String>]
#
# @api public
def column_titles
[
'Direct Deposit/Check',
'Cust. Name',
'New/Existing Cust.',
'Email',
'Phone',
'Address',
'Facility Booked',
'Booking Source',
'Check In',
'Check Out',
'Nights Booked',
'Pet Name',
'Breed',
'Discount Amount',
'Amount With Discount Applied',
'Amount Before Discount Applied',
'Promotion used?',
'Promotion Used',
'Promotion Type',
'Booking Fee',
'Total Amount After Fee',
'Room(s) type booked'
]
end
# Stat row methods for internal booking report
#
# @return [Array<Symbols>]
#
# @api public
def stat_row_methods
[
:banking?,
:user_full_name,
:new_customer?,
:user_email,
:user_phone,
:address,
:location_name,
:booking_source,
:display_check_in,
:display_check_out,
:nights,
:pets_names,
:pets_breeds,
:discount,
:total_price,
:total_with_discount,
:promo_code?,
:promotional_code,
:promotional_type,
:booking_fee,
:total_after_fee,
:room_names
]
end
# Temporary path for report generation
#
# @return [String]
#
# @api public
def file_path
"#{Rails.root}/tmp/internal_report_#{date.to_s.underscore}.xls"
end
end
class InternalReportPresenter < BookingPresenter
delegate :phone, :email, :full_name, to: :user, prefix: true
delegate :names, :breeds, to: :pets, prefix: true
OBT_FEE = 0.08
WEB_FEE = 0.12
# Has banking information
#
# @return [Boolean]
#
# @api public
def banking?
business_entity.banking.present?.to_s
end
# Check in date
#
# @return [String]
#
# @api public
def display_check_in
check_in.strftime('%-m/%-d/%-y')
end
# Check out date
#
# @return [String]
#
# @api public
def display_check_out
check_out.strftime('%-m/%-d/%-y')
end
# Does the customer have a booking at an existing facility
#
# @return [Boolean]
#
# @api public
def new_customer?
user_bookings.where(boarding_facility_id: boarding_facility_id).count <= 1
end
# Source of the booking
#
# @return [String]
#
# @api public
def booking_source
source == 'widget' || source.nil? ? 'OBT' : 'Web'
end
# Total booking price with discount amount
#
# @return [Integer]
#
# @api public
def total_with_discount
total_price + discount.to_f
end
# Promotion code used
#
# @return [String]
#
# @api public
def promotional_code
promo_code ? promo_code.code : ''
end
# Promotion type used
#
# @return [String]
#
# @api public
def promotional_type
promo_code ? promo_code.discount_type : ''
end
# Booking fee
#
# @return [Float]
#
# @api public
def booking_fee
total_price * discount_amount
end
# Total to be payed out to BusinessEntity
#
# @return [Float]
#
# @api public
def total_after_fee
total_price - booking_fee
end
private
# Discount amount from obt or web
#
# @return [Float]
#
# @api public
def discount_amount
online_booking_tool? ? OBT_FEE : WEB_FEE
end
# is the booking_source OBT
#
# @return [Boolean]
#
# @api private
def online_booking_tool?
booking_source.eql?('OBT')
end
# Users bookings
#
# @return [ActiveRecord::Collection]
#
# @api private
def user_bookings
user.bookings
end
end
class Report < ActiveRecord::Base
belongs_to :business_entity
has_attached_file :file, REPORT_STORAGE
do_not_validate_attachment_file_type :file
# Generate the booking report for specific business_entity
# Used in facility dashboard
#
# @return [Boolean]
#
# @api public
def self.generate_booking_report(business_entity, year = Date.current.year)
find_or_initialize_by(year: year, business_entity_id: business_entity.id).
generate_and_record_spreadsheet(
Booking::ReportGenerator.new(
business_entity: business_entity,
bookings: business_entity.bookings
)
)
end
# Generate an internal booking report
#
# @return [Boolean]
#
# @api public
def self.generate_internal_report(bookings, year = Date.current.year)
find_or_initialize_by(year: year, business_entity_id: nil).
generate_and_record_spreadsheet(
Internal::ReportGenerator.new(
bookings: bookings
)
)
end
# Generate and record the spreadsheet as a report
#
# @return [Boolean]
#
# @api private
def generate_and_record_spreadsheet(spreadsheet)
spreadsheet.generate
spreadsheet.record_file
self.file = File.open(spreadsheet.file_path)
File.delete(spreadsheet.file_path)
save
self
end
end
# A spreadsheet is made up of multiple components
#
# Workbook is the main component
# Worksheets are housed within a workbook to generate the tabs in the xls file
# Rows are appended to a worksheet by assigning variables to worksheet[x, y]
# where [x, y] is the row & column
class ReportGenerator
extend Memoist
attr_reader :bookings, :date, :business_entity
def initialize(args)
@business_entity = args.fetch(:business_entity) { nil }
@bookings = args.fetch(:bookings)
@date = Date.current
end
# Column titles
#
# @return [String]
#
# @api public
def column_titles
raise NotImplementedError, 'define column_titles method in the subclass'
end
# Stat row methods
#
# @return [String]
#
# @api public
def stat_row_methods
raise NotImplementedError, 'define stat_row_methods method in the subclass'
end
# File Path for report upload
#
# @return [String]
#
# @api public
def file_path
raise NotImplementedError, 'define file_path method in the subclass'
end
# Raise not implemented error if presenter class not defined
#
# @return [String]
#
# @api public
def presenter_class
raise NotImplementedError, 'define presenter_class in the subclass'
end
# Create the worksheet for the report
#
# @param [month<Integer>]
# zero-index
# ex.) Date.current.beginning_of_month + 0.month = Jan 1, 2015
#
# @return [Integer]
#
# @api private
def worksheet(i)
month = start_date + i.months
name = "#{month.strftime('%B')} #{month.year}"
sheet = workbook.create_worksheet name: name
generate_title_row(sheet)
bookings.bookings_for_month(month).each_with_index do |booking, index|
generate_stat_row(
sheet,
presenter_class.new(self, booking),
index + 2
)
end
end
# Generate spreadsheet for each month of the year
#
# @return [Integer]
#
# @api public
def generate
12.times { |month| worksheet(month) }
end
# Generate the title row
#
# @return []
#
# @api public
def generate_title_row(sheet)
sheet[0, 0] = 'Bookings'
titles = column_titles
titles.delete('Source') unless business_entity && business_entity.widget
titles.each_with_index { |title, index| sheet[1, index] = title }
end
# Generate Stat Row with information from booking
#
# @return []
#
# @api public
def generate_stat_row(sheet, booking, row)
methods = stat_row_methods
methods.delete(:source) unless business_entity && business_entity.widget
methods.each_with_index do |method, index|
sheet[row, index] = booking.send(method)
end
end
# Record the workbook to the filepath
#
# @return []
#
# @api public
def record_file
workbook.write file_path
end
private
# Beginning of Year
#
# @return [Date]
#
# @api private
def start_date
date.beginning_of_year
end
# Build a new spreadsheet
#
# @return [Spreadsheet::Workbook]
#
# @api private
def workbook
Spreadsheet::Workbook.new
end
memoize :workbook,
:start_date
end
class ReportPresenter < BookingPresenter
delegate :phone, :email, :full_name, to: :user, prefix: true
delegate :names, :breeds, to: :pets, prefix: true
# Users full address
#
# @return [String]
#
# @api public
def user_full_address
address = "#{user.address1} #{user.address2} #{user.city} #{user.state} #{user.zip}"
address.blank? ? 'N/A' : address.strip
end
# Boarding Facility Address For Reports
#
# @return [String]
#
# @api public
def location_address
"#{location_name} - #{boarding_facility.city_name}"
end
# Booking total price with discount applied
#
# @return [String]
#
# @api public
def total_price_with_discount
number_to_currency(total_price + discount.to_f, precision: 2)
end
# display room names
#
# @return [String]
#
# @api public
def room_names
super.join(', ')
end
def display_check_in
check_in.strftime('%-m/%-d/%-y')
end
def display_check_out
check_out.strftime('%-m/%-d/%-y')
end
end
class Report < ActiveRecord::Base
attr_accessible :kennel_id, :year, :file_file_name, :file_content_type, :file_file_size, :file_updated_at
belongs_to :kennel
# has_attached_file :file, {url: "/system/:class/:id/:filename"}
has_attached_file :file, REPORT_STORAGE
def generate
start_date = Date.parse("#{year}-01-01")
end_date = Date.parse("#{year}-12-31")
book = Spreadsheet::Workbook.new
12.times do |i|
month = start_date + i.months
sheet = book.create_worksheet name: "#{month.strftime("%B")} #{month.year}"
sheet[0,0] = "Bookings"
sheet[1,0] = "Cust. Name"
sheet[1,1] = "Email"
sheet[1,2] = "Phone"
sheet[1,3] = "Address"
sheet[1,4] = "Location"
sheet[1,5] = "Check in"
sheet[1,6] = "Check out"
sheet[1,7] = "Room"
sheet[1,8] = "Pet Name"
sheet[1,9] = "Breed"
sheet[1,10] = "# Nights"
if kennel.widget
sheet[1,11] = "Source"
sheet[1,12] = "Amount"
else
sheet[1,11] = "Amount"
end
row = 2
kennel.bookings.where("created_at >= ? AND created_at <= ?",
month.beginning_of_month.beginning_of_day,
month.end_of_month.end_of_day).each do |b|
user = b.user
sheet[row,0] = user.full_name
sheet[row,1] = user.email
sheet[row,2] = user.phone
sheet[row,3] = "#{user.address1} #{user.address2} #{user.city} #{user.state}, #{user.zip}"
sheet[row,4] = "#{b.location.name}- #{b.location.city_name}" if b.location
sheet[row,5] = b.check_in.strftime("%m/%d/%y")
sheet[row,6] = b.check_out.strftime("%m/%d/%y")
sheet[row,7] = b.room.name if b.room
sheet[row,8] = b.pets.map{|x| x.name}.join(",") if b.pets.any?
sheet[row,9] = b.pets.map{|x| x.breed}.join(",") if b.pets.any?
sheet[row,10] = b.nights
if kennel.widget
sheet[row,11] = b.source
sheet[row,12] = "$#{b.total_price + b.discount.to_f}"
else
sheet[row,11] = "$#{b.total_price + b.discount.to_f}"
end
row += 1
end
end
file_path = "#{Rails.root}/tmp/#{kennel.name.downcase.gsub(" ", "-").gsub(/[^a-zA-Z\d\s:-]/, '')}_#{year}_report.xls"
book.write file_path
self.file = File.open(file_path)
File.delete(file_path)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment