Merge branch '266reportsbranch' of https://github.com/lin-d-hop/openfoodnetwork into lin-d-hop-266reportsbranch

Conflicts:
	app/controllers/spree/admin/reports_controller_decorator.rb
	spec/models/spree/order_spec.rb
This commit is contained in:
Rohan Mitchell
2014-12-19 09:38:02 +11:00
11 changed files with 312 additions and 6 deletions

View File

@@ -5,6 +5,7 @@ require 'open_food_network/group_buy_report'
require 'open_food_network/order_grouper'
require 'open_food_network/customers_report'
require 'open_food_network/users_and_enterprises_report'
require 'open_food_network/order_cycle_management_report'
Spree::Admin::ReportsController.class_eval do
@@ -22,11 +23,14 @@ Spree::Admin::ReportsController.class_eval do
customers: [
["Mailing List", :mailing_list],
["Addresses", :addresses]
],
order_cycle_management: [
["Payment Methods Report", :payment_methods_report]
]
}
# Fetches user's distributors, suppliers and order_cycles
before_filter :load_data, only: [:customers, :products_and_inventory]
before_filter :load_data, only: [:customers, :products_and_inventory, :order_cycle_management]
# Render a partial for orders and fulfillment description
respond_override :index => { :html => { :success => lambda {
@@ -36,6 +40,8 @@ Spree::Admin::ReportsController.class_eval do
render_to_string(partial: 'products_and_inventory_description', layout: false, locals: {report_types: REPORT_TYPES[:products_and_inventory]}).html_safe
@reports[:customers][:description] =
render_to_string(partial: 'customers_description', layout: false, locals: {report_types: REPORT_TYPES[:customers]}).html_safe
@reports[:order_cycle_management][:description] =
render_to_string(partial: 'order_cycle_management_description', layout: false, locals: {report_types: REPORT_TYPES[:order_cycle_management]}).html_safe
} } }
@@ -54,6 +60,18 @@ Spree::Admin::ReportsController.class_eval do
render_report(@report.header, @report.table, params[:csv], "customers.csv")
end
def order_cycle_management
@report_types = REPORT_TYPES[:order_cycle_management]
@report_type = params[:report_type]
@report = OpenFoodNetwork::OrderCycleManagementReport.new spree_current_user, params
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
@orders = @search.result
render_report(@report.header, @report.table, params[:csv], "customers.csv")
end
def orders_and_distributors
params[:q] = {} unless params[:q]
@@ -621,6 +639,7 @@ Spree::Admin::ReportsController.class_eval do
:products_and_inventory => {:name => "Products & Inventory", :description => ''},
:sales_total => { :name => "Sales Total", :description => "Sales Total For All Orders" },
:users_and_enterprises => { :name => "Users & Enterprises", :description => "Enterprise Ownership & Status" }
:order_cycle_management => {:name => "Order Cycle Management", :description => ''}
}
# Return only reports the user is authorized to view.
reports.select { |action| can? action, :report }

View File

@@ -7,5 +7,14 @@ module Spree
[ "#{oc.name}   (#{orders_open_at} - #{orders_close_at})".html_safe, oc.id ]
end
end
def report_payment_method_options(orders)
orders.map { |o| o.payments.first.payment_method.andand.name }.uniq
end
def report_shipping_options(orders)
orders.map { |o| o.shipping_method.andand.name }.uniq
end
end
end

View File

@@ -128,7 +128,7 @@ class AbilityDecorator
end
# Reports page
can [:admin, :index, :customers, :group_buys, :bulk_coop, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory], :report
can [:admin, :index, :customers, :orders_and_distributors, :group_buys, :bulk_coop, :payments, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management], :report
end

View File

@@ -66,6 +66,12 @@ Spree::Order.class_eval do
where("state != ?", state)
}
scope :with_payment_method_name, lambda { |payment_method_name|
joins(:payments => :payment_method).
where('spree_payment_methods.name = ?', payment_method_name).
select('DISTINCT spree_orders.*')
}
# -- Methods
def products_available_from_new_distribution

View File

@@ -0,0 +1,4 @@
%ul{style: "margin-left: 12pt"}
- report_types.each do |report_type|
%li
= link_to report_type[0], "#{order_cycle_management_admin_reports_url}?report_type=#{report_type[1]}"

View File

@@ -0,0 +1,44 @@
= form_tag spree.order_cycle_management_admin_reports_url do |f|
%br
= label_tag nil, "Order Cycle: "
= select_tag(:order_cycle_id,
options_for_select(report_order_cycle_options(@order_cycles), params[:order_cycle_id]),
include_blank: true)
%br
%br
= label_tag nil, "Payment Methods (hold Ctrl to select multiple payment methods)"
%br
= select_tag(:payment_method_name,
options_for_select(report_payment_method_options(@orders), params[:payment_method_name]),
multiple: true, include_blank: true)
%br
%br
= label_tag nil, "Shipping Method: "
= select_tag(:shipping_method_name,
options_for_select(report_shipping_options(@orders), params[:shipping_method_name]),
include_blank: true)
%br
%br
= check_box_tag :csv
= label_tag :csv, "Download as csv"
%br
%br
= button t(:search)
%br
%br
%table#listing_order_payment_methods.index
%thead
%tr{'data-hook' => "orders_header"}
- @report.header.each do |heading|
%th=heading
%tbody
- @report.table.each do |row|
%tr
- row.each do |column|
%td= column
- if @report.table.empty?
%tr
%td{:colspan => "2"}= t(:none)

View File

@@ -116,6 +116,7 @@ end
Spree::Core::Engine.routes.prepend do
match '/admin/reports/orders_and_distributors' => 'admin/reports#orders_and_distributors', :as => "orders_and_distributors_admin_reports", :via => [:get, :post]
match '/admin/reports/order_cycle_management' => 'admin/reports#order_cycle_management', :as => "order_cycle_management_admin_reports", :via => [:get, :post]
match '/admin/reports/group_buys' => 'admin/reports#group_buys', :as => "group_buys_admin_reports", :via => [:get, :post]
match '/admin/reports/bulk_coop' => 'admin/reports#bulk_coop', :as => "bulk_coop_admin_reports", :via => [:get, :post]
match '/admin/reports/payments' => 'admin/reports#payments', :as => "payments_admin_reports", :via => [:get, :post]

View File

@@ -0,0 +1,65 @@
module OpenFoodNetwork
class OrderCycleManagementReport
attr_reader :params
def initialize(user, params = {})
@params = params
@user = user
end
def header
["First Name", "Last Name", "Email", "Phone", "Hub", "Shipping Method", "Payment Method", "Amount ", "Amount Paid"]
end
def table
orders.map do |order|
ba = order.billing_address
da = order.distributor.andand.address
[ba.firstname,
ba.lastname,
order.email,
ba.phone,
order.distributor.andand.name,
order.shipping_method.andand.name,
order.payments.first.andand.payment_method.andand.name,
order.payments.first.amount
]
end
end
def orders
filter Spree::Order.managed_by(@user).distributed_by_user(@user).complete.where("spree_orders.state != ?", :canceled)
end
def filter(orders)
filter_to_order_cycle filter_to_payment_method filter_to_shipping_method orders
end
def filter_to_payment_method (orders)
if params[:payment_method_name].present?
orders.with_payment_method_name(params[:payment_method_name])
else
orders
end
end
def filter_to_shipping_method (orders)
if params[:shipping_method_name].present?
orders.joins(:shipping_method).where("spree_shipping_methods.name = ?", params[:shipping_method_name])
else
orders
end
end
def filter_to_order_cycle(orders)
if params[:order_cycle_id].present?
orders.where(order_cycle_id: params[:order_cycle_id])
else
orders
end
end
end
end

View File

@@ -59,6 +59,22 @@ feature %q{
end
end
describe "Order cycle management report" do
before do
login_to_admin_section
click_link "Reports"
end
scenario "order payment method report" do
click_link "Order Cycle Management"
rows = find("table#listing_order_payment_methods").all("thead tr")
table = rows.map { |r| r.all("th").map { |c| c.text.strip } }
table.sort.should == [
["First Name", "Last Name", "Email", "Phone", "Hub", "Payment Method", "Amount", "Amount Paid"]
].sort
end
end
scenario "orders and distributors report" do
login_to_admin_section
click_link 'Reports'

View File

@@ -0,0 +1,121 @@
require 'spec_helper'
include AuthenticationWorkflow
module OpenFoodNetwork
describe OrderCycleManagementReport do
context "as a site admin" do
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by_name!("admin")
user
end
subject { OrderCycleManagementReport.new user }
describe "fetching orders" do
it "fetches completed orders" do
o1 = create(:order)
o2 = create(:order, completed_at: 1.day.ago)
subject.orders.should == [o2]
end
it "does not show cancelled orders" do
o1 = create(:order, state: "canceled", completed_at: 1.day.ago)
o2 = create(:order, completed_at: 1.day.ago)
subject.orders.should == [o2]
end
end
end
context "as an enterprise user" do
let!(:user) { create_enterprise_user }
subject { OrderCycleManagementReport.new user }
describe "fetching orders" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:order) { create(:order, completed_at: 1.day.ago) }
it "only shows orders managed by the current user" do
d1 = create(:distributor_enterprise)
d1.enterprise_roles.build(user: user).save
d2 = create(:distributor_enterprise)
d2.enterprise_roles.build(user: create(:user)).save
o1 = create(:order, distributor: d1, completed_at: 1.day.ago)
o2 = create(:order, distributor: d2, completed_at: 1.day.ago)
subject.should_receive(:filter).with([o1]).and_return([o1])
subject.orders.should == [o1]
end
it "does not show orders through a hub that the current user does not manage" do
# Given a supplier enterprise with an order for one of its products
supplier.enterprise_roles.build(user: user).save
order.line_items << create(:line_item, product: product)
# When I fetch orders, I should see no orders
subject.should_receive(:filter).with([]).and_return([])
subject.orders.should == []
end
end
describe "filtering orders" do
let!(:orders) { Spree::Order.scoped }
let!(:supplier) { create(:supplier_enterprise) }
let!(:oc1) { create(:simple_order_cycle) }
let!(:pm1) { create(:payment_method, name: "PM1") }
let!(:sm1) { create(:shipping_method, name: "ship1") }
let!(:order1) { create(:order, shipping_method: sm1, order_cycle: oc1) }
let!(:payment1) { create(:payment, order: order1, payment_method: pm1) }
it "returns all orders sans-params" do
subject.filter(orders).should == orders
end
it "filters to a specific order cycle" do
oc2 = create(:simple_order_cycle)
order2 = create(:order, order_cycle: oc2)
subject.stub(:params).and_return(order_cycle_id: oc1.id)
subject.filter(orders).should == [order1]
end
it "filters to a payment method" do
pm2 = create(:payment_method, name: "PM2")
order2 = create(:order)
payment2 = create(:payment, order: order2, payment_method: pm2)
subject.stub(:params).and_return(payment_method_name: pm1.name)
subject.filter(orders).should == [order1]
end
it "filters to a shipping method" do
sm2 = create(:shipping_method, name: "ship2")
order2 = create(:order, shipping_method: sm2)
subject.stub(:params).and_return(shipping_method_name: sm1.name)
subject.filter(orders).should == [order1]
end
it "should do all the filters at once" do
subject.stub(:params).and_return(
order_cycle_id: oc1.id,
shipping_method_name: sm1.name,
payment_method_name: pm1.name)
subject.filter(orders).should == [order1]
end
end
end
end
end

View File

@@ -77,7 +77,7 @@ describe Spree::Order do
subject.update_distribution_charge!
end
it "ensures the correct adjustment(s) are created for order cycles" do
it "ensures the correct adjustment(s) are created for order cycles" do
EnterpriseFee.stub(:clear_all_adjustments_on_order)
line_item = double(:line_item)
subject.stub(:line_items) { [line_item] }
@@ -328,15 +328,37 @@ describe Spree::Order do
end
end
describe "scopes" do
describe "scopes" do
describe "not_state" do
it "finds only orders not in specified state" do
o = FactoryGirl.create(:completed_order_with_totals)
o.cancel!
Spree::Order.not_state(:canceled).should_not include o
end
end
describe "with payment method name" do
let!(:o1) { create(:order) }
let!(:o2) { create(:order) }
let!(:pm1) { create(:payment_method, name: 'foo') }
let!(:pm2) { create(:payment_method, name: 'bar') }
let!(:p1) { create(:payment, order: o1, payment_method: pm1) }
let!(:p2) { create(:payment, order: o2, payment_method: pm2) }
it "returns the order with payment method name" do
Spree::Order.with_payment_method_name('foo').should == [o1]
end
it "doesn't return rows with a different payment method name" do
Spree::Order.with_payment_method_name('foobar').should_not include o1
Spree::Order.with_payment_method_name('foobar').should_not include o2
end
it "doesn't return duplicate rows" do
p2 = FactoryGirl.create(:payment, :order => o1, :payment_method => pm1)
Spree::Order.with_payment_method_name('foo').length.should == 1
end
end
end
describe "shipping address prepopulation" do
@@ -385,5 +407,4 @@ describe Spree::Order do
create(:order).deliver_order_confirmation_email
end
end
end