module Spree
class AdvancedReport
include Ruport
attr_accessor :orders, :product_text, :date_text, :taxon_text, :ruportdata, :search,
:data, :params, :taxon, :product, :product_in_taxon, :unfiltered_params
def name
I18n.t("adv_report.base.name")
end
def description
I18n.t("adv_report.base.description")
end
def initialize(params)
# this enables subclasses to provide different defaults to the search
# by setting the defaults before calling super
self.params ||= params
self.data = {}
self.ruportdata = {}
self.unfiltered_params = params[:search].blank? ? {} : params[:search].clone
params[:search] ||= {}
params[:advanced_reporting] ||= {}
if Order.count > 0
begin
params[:search][:created_at_gt] = Time.zone.parse(params[:search][:created_at_gt]).beginning_of_day
rescue
params[:search][:created_at_gt] = Date.today.beginning_of_day
end
# TODO if lt is defined, and gt is not, gt then should use better default than end of today
# maybe 24 hours before the defined lt end of day
begin
params[:search][:created_at_lt] = Time.zone.parse(params[:search][:created_at_lt]).end_of_day
rescue
params[:search][:created_at_lt] = Date.today.end_of_day
end
end
# offer shipped vs completed order filtering
# in some cases, revenue reports should be based on the time when the revenue
# is earned (i.e. shipped) not when the order was made or the credit card was processed
# it is also important to exclude canceled orders and orders that were not completed
# before spree 1.1.3 there was a bug that caused Spree::Shipment.shipped_at not be filled
# easy fix is to copy the completed_at from the order associated with the shipment
# https://gist.github.com/3187793#file_shipments_shipped_at_fix.rb
filter_address = 'billing'
if params[:advanced_reporting][:state_based_on_taxable_address] == '1'
filter_address = Spree::Config[:tax_using_ship_address] ? 'shipping' : 'billing'
end
if params[:advanced_reporting][:order_type] == 'shipped'
shipped_search_params = {
:shipped_at_gt => params[:search][:created_at_gt],
:shipped_at_lt => params[:search][:created_at_lt],
:order_state_not_eq => 'canceled',
:order_completed_at_not_null => true
}
if params[:advanced_reporting][:state_id].present?
shipped_search_params[
filter_address == 'shipping' ? :order_ship_address_state_id_eq : :order_bill_address_state_id_eq
] = params[:advanced_reporting][:state_id]
end
# including the ransack predicate will not speed up the SQL query but will not include only fully shipped orders
only_fully_shipped = params[:advanced_reporting][:shipment] == 'fully_shipped'
shipped_search_params[:order_inventory_units_shipment_id_not_null] = true if only_fully_shipped
# the tricky part here is that orders can have multiple shipments
# we need to prevent orders from being included twice in the report
# by choosing to include the order in the earliest report possible
# (i.e. the first order that shipped) and exclude it from any reports after that
@search = Shipment.includes(:order).search shipped_search_params
self.orders = @search.result(:distinct => true).select do |shipment|
# these manual exclusions could not be done via SQL queries as far as I could tell
# they are ordered by least to greatest SQL complexity
next true if shipment.order.shipments.size == 1
# if the shipment retrieved is the last shipment shipped for the order, then include the order
next false if shipment.order.shipments.sort { |a, b| b.shipped_at <=> a.shipped_at }.first == shipment
# conditionally exclude orders which are not fully shipped
next false if only_fully_shipped && shipment.order.inventory_units.detect { |i| i.shipment.blank? }.blank?
true
end.map(&:order)
else
params[:search][:completed_at_not_null] = true
params[:search][:state_not_eq] = 'canceled'
if params[:advanced_reporting][:state_id].present?
params[:search][
filter_address == 'shipping' ? :ship_address_state_id_eq : :bill_address_state_id_eq
] = params[:advanced_reporting][:state_id]
end
only_fully_shipped = params[:advanced_reporting][:shipment] == 'fully_shipped'
params[:inventory_units_shipment_id_not_null] = true if only_fully_shipped
@search = Order.search(params[:search])
self.orders = @search.result(:distinct => true).select do |order|
next false if only_fully_shipped && order.inventory_units.detect { |i| i.shipment.blank? }.blank?
true
end
end
self.product_in_taxon = true
if params[:advanced_reporting]
if params[:advanced_reporting][:taxon_id] && params[:advanced_reporting][:taxon_id] != ''
self.taxon = Taxon.find(params[:advanced_reporting][:taxon_id])
end
if params[:advanced_reporting][:product_id] && params[:advanced_reporting][:product_id] != ''
self.product = Product.find(params[:advanced_reporting][:product_id])
end
end
if self.taxon && self.product && !self.product.taxons.include?(self.taxon)
self.product_in_taxon = false
end
if self.product
self.product_text = "Product: #{self.product.name}
"
end
if self.taxon
self.taxon_text = "Taxon: #{self.taxon.name}
"
end
# Above searchlogic date settings
self.date_text = "#{I18n.t("adv_report.base.range")}:"
if self.unfiltered_params
if self.unfiltered_params[:created_at_gt] != '' && self.unfiltered_params[:created_at_lt] != ''
self.date_text += " From #{self.unfiltered_params[:created_at_gt]} to #{self.unfiltered_params[:created_at_lt]}"
elsif self.unfiltered_params[:created_at_gt] != ''
self.date_text += " After #{self.unfiltered_params[:created_at_gt]}"
elsif self.unfiltered_params[:created_at_lt] != ''
self.date_text += " Before #{self.unfiltered_params[:created_at_lt]}"
# TODO this was pulled in from another branch and has some nice internationalization improvements
# if self.unfiltered_params[:created_at_greater_than] != '' && self.unfiltered_params[:created_at_less_than] != ''
# self.date_text += " #{I18n.t("adv_report.base.from")} #{self.unfiltered_params[:created_at_greater_than]} to #{self.unfiltered_params[:created_at_less_than]}"
# elsif self.unfiltered_params[:created_at_greater_than] != ''
# self.date_text += " #{I18n.t("adv_report.base.after")} #{self.unfiltered_params[:created_at_greater_than]}"
# elsif self.unfiltered_params[:created_at_less_than] != ''
# self.date_text += " #{I18n.t("adv_report.base.before")} #{self.unfiltered_params[:created_at_less_than]}"
else
self.date_text += " #{I18n.t("adv_report.base.all")}"
end
else
self.date_text += " #{I18n.t("adv_report.base.all")}"
end
end
def download_url(base, format, report_type = nil)
elements = []
params[:advanced_reporting] ||= {}
params[:advanced_reporting]["report_type"] = report_type if report_type
if params
[:search, :advanced_reporting].each do |type|
if params[type]
params[type].each { |k, v| elements << "#{type}[#{k}]=#{v}" }
end
end
end
base.gsub!(/^\/\//,'/')
base + '.' + format + '?' + elements.join('&')
end
def revenue(order)
rev = order.item_total
if !self.product.nil? && product_in_taxon
rev = order.line_items.select { |li| li.product == self.product }.inject(0) { |a, b| a += b.quantity * b.price }
elsif !self.taxon.nil?
rev = order.line_items.select { |li| li.product && li.product.taxons.include?(self.taxon) }.inject(0) { |a, b| a += b.quantity * b.price }
end
adjustment_revenue = order.adjustments.sum(:amount)
rev += adjustment_revenue if rev > 0
self.product_in_taxon ? rev : 0
end
def profit(order)
profit = order.line_items.inject(0) { |profit, li| profit + (li.variant.price - li.variant.cost_price.to_f)*li.quantity }
if !self.product.nil? && product_in_taxon
profit = order.line_items.select { |li| li.product == self.product }.inject(0) { |profit, li| profit + (li.variant.price - li.variant.cost_price.to_f)*li.quantity }
elsif !self.taxon.nil?
profit = order.line_items.select { |li| li.product && li.product.taxons.include?(self.taxon) }.inject(0) { |profit, li| profit + (li.variant.price - li.variant.cost_price.to_f)*li.quantity }
end
profit += order.adjustments.sum(:amount)
self.product_in_taxon ? profit : 0
end
def units(order)
units = order.line_items.sum(:quantity)
if !self.product.nil? && product_in_taxon
units = order.line_items.select { |li| li.product == self.product }.inject(0) { |a, b| a += b.quantity }
elsif !self.taxon.nil?
units = order.line_items.select { |li| li.product && li.product.taxons.include?(self.taxon) }.inject(0) { |a, b| a += b.quantity }
end
self.product_in_taxon ? units : 0
end
def order_count(order)
self.product_in_taxon ? 1 : 0
end
def date_range
if self.params[:search][:created_at_gt].to_date == self.params[:search][:created_at_lt].to_date
self.params[:search][:created_at_gt].to_date.to_s
else
"#{self.params[:search][:created_at_gt].to_date} – #{self.params[:search][:created_at_lt].to_date}"
end
end
end
end