Auto-merge from CI [skip ci]

This commit is contained in:
Continuous Integration
2015-07-29 12:03:03 +10:00
26 changed files with 1012 additions and 577 deletions

View File

@@ -50,6 +50,7 @@ gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg'
gem 'angularjs-file-upload-rails', '~> 1.1.0'
gem 'roadie-rails', '~> 1.0.3'
gem 'figaro'
gem 'blockenspiel'
gem 'acts-as-taggable-on', '~> 3.4'
gem 'foreigner'

View File

@@ -165,6 +165,7 @@ GEM
bcrypt (3.1.7)
bcrypt-ruby (3.1.5)
bcrypt (>= 3.1.3)
blockenspiel (0.4.5)
bugsnag (1.5.2)
httparty (>= 0.6, < 1.0)
multi_json (~> 1.0)
@@ -546,6 +547,7 @@ DEPENDENCIES
angularjs-rails (= 1.2.13)
awesome_print
aws-sdk
blockenspiel
bugsnag
capybara
coffee-rails (~> 3.2.1)

View File

@@ -15,7 +15,7 @@ We're part of global movement - get involved!
## Dependencies
* Rails 3.2.x
* Ruby >= 1.9.3
* Ruby 1.9.3
* PostgreSQL database
* PhantomJS (for testing)
* See Gemfile for a list of gems required
@@ -32,19 +32,20 @@ You can view the code at:
You can download the source with the command:
git clone git@github.com:openfoodfoundation/openfoodnetwork
git clone https://github.com/openfoodfoundation/openfoodnetwork.git
## Get it running
For those new to Rails, the following tutorial will help get you up to speed with configuring a Rails environment: http://guides.rubyonrails.org/getting_started.html .
First, check your dependencies: Ensure that you have Ruby 1.9.x installed:
First, check your dependencies: Ensure that you have Ruby >= 1.9.3 installed:
ruby --version
Install the project's gem dependencies:
cd openfoodnetwork
bundle install
Configure the site:

View File

@@ -9,8 +9,8 @@ Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal, visibleFilter) ->
# Adding methods to each enterprise
extend: (enterprise) ->
new class MapMarker
# We're whitelisting attributes because GMaps tries to crawl
# our data, and our data is recursive, so it breaks
# We cherry-pick attributes because GMaps tries to crawl
# our data, and our data is cyclic, so it breaks
latitude: enterprise.latitude
longitude: enterprise.longitude
icon: enterprise.icon

View File

@@ -1,4 +1,5 @@
%ng-include{src: "'partials/enterprise_header.html'"}
%ng-include{src: "'partials/enterprise_details.html'"}
%ng-include{src: "'partials/hub_details.html'"}
%ng-include{src: "'partials/producer_details.html'"}
%ng-include{src: "'partials/close.html'"}

View File

@@ -0,0 +1,23 @@
-# Show places to buy products from this producer, when there are any
-# Do not show this for producer shops selling only their own produce,
-# Since a shopping link will already have been displayed in hub_details.html.haml
.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
.columns.small-12
.row
.columns.small-12.fat
%div{"bo-if" => "enterprise.name"}
%label
Shop for
%span.turquoise{"bo-text" => "enterprise.name"}
products at:
%div.show-for-medium-up{"bo-if" => "!enterprise.name"}
&nbsp;
.row.cta-container
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
"bo-href" => "hub.path", "ofn-empties-cart" => "hub",
"bo-class" => "{primary: hub.active, secondary: !hub.active}"}
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
.hub-name{"bo-text" => "hub.name"}
.button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}

View File

@@ -9,6 +9,9 @@ require 'open_food_network/order_cycle_management_report'
require 'open_food_network/packing_report'
require 'open_food_network/sales_tax_report'
require 'open_food_network/xero_invoices_report'
require 'open_food_network/bulk_coop_report'
require 'open_food_network/payments_report'
require 'open_food_network/orders_and_fulfillments_report'
Spree::Admin::ReportsController.class_eval do
@@ -83,17 +86,8 @@ Spree::Admin::ReportsController.class_eval do
end
def packing
# -- Prepare parameters
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]) rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]) rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
# -- Prepare date parameters
prepare_date_params params
# -- Prepare form options
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
@@ -117,18 +111,7 @@ Spree::Admin::ReportsController.class_eval do
end
def orders_and_distributors
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
prepare_date_params params
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
orders = @search.result
@@ -146,18 +129,7 @@ Spree::Admin::ReportsController.class_eval do
end
def sales_tax
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
prepare_date_params params
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
orders = @search.result
@@ -176,300 +148,47 @@ Spree::Admin::ReportsController.class_eval do
end
def bulk_coop
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
orders = @search.result
@line_items = orders.map { |o| o.line_items.managed_by(spree_current_user) }.flatten
# -- Prepare date parameters
prepare_date_params params
# -- Prepare form options
@distributors = Enterprise.is_distributor.managed_by(spree_current_user)
@report_type = params[:report_type]
case params[:report_type]
when "bulk_coop_supplier_report"
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::BulkCoopReport.new spree_current_user, params
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
@table = order_grouper.table(@report.table_items)
csv_file_name = "bulk_coop_#{params[:report_type]}_#{timestamp}.csv"
header = ["Supplier", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Units Required", "Remainder"]
columns = [ proc { |lis| lis.first.variant.product.supplier.name },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| lis.first.variant.full_name },
proc { |lis| lis.first.variant.weight || 0 },
proc { |lis| lis.sum { |li| li.quantity } },
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
proc { |lis| "" },
proc { |lis| "" } ]
rules = [ { group_by: proc { |li| li.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |li| li.variant.product },
sort_by: proc { |product| product.name },
summary_columns: [ proc { |lis| lis.first.variant.product.supplier.name },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| "" },
proc { |lis| "" },
proc { |lis| lis.sum { |li| (li.quantity || 0) * (li.variant.weight || 0) } },
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor },
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
{ group_by: proc { |li| li.variant },
sort_by: proc { |variant| variant.full_name } } ]
when "bulk_coop_allocation"
header = ["Customer", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Total Allocated", "Remainder"]
columns = [ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| lis.first.variant.full_name },
proc { |lis| lis.first.variant.weight || 0 },
proc { |lis| lis.sum { |li| li.quantity } },
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
proc { |lis| "" },
proc { |lis| "" } ]
rules = [ { group_by: proc { |li| li.variant.product },
sort_by: proc { |product| product.name },
summary_columns: [ proc { |lis| "TOTAL" },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| "" },
proc { |lis| "" },
proc { |lis| lis.sum { |li| li.quantity * (li.variant.weight || 0) } },
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) },
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
{ group_by: proc { |li| li.variant },
sort_by: proc { |variant| variant.full_name } },
{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.to_s } } ]
when "bulk_coop_packing_sheets"
header = ["Customer", "Product", "Variant", "Sum Total"]
columns = [ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.full_name },
proc { |lis| lis.sum { |li| li.quantity } } ]
rules = [ { group_by: proc { |li| li.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |li| li.variant },
sort_by: proc { |variant| variant.full_name } },
{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.to_s } } ]
when "bulk_coop_customer_payments"
header = ["Customer", "Date of Order", "Total Cost", "Amount Owing", "Amount Paid"]
columns = [ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
proc { |lis| lis.first.order.completed_at.to_s },
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.total } },
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.outstanding_balance } },
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.payment_total } } ]
rules = [ { group_by: proc { |li| li.order },
sort_by: proc { |order| order.completed_at } } ]
else # List all line items
header = ["Supplier", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Units Required", "Remainder"]
columns = [ proc { |lis| lis.first.variant.product.supplier.name },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| lis.first.variant.full_name },
proc { |lis| lis.first.variant.weight || 0 },
proc { |lis| lis.sum { |li| li.quantity } },
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
proc { |lis| "" },
proc { |lis| "" } ]
rules = [ { group_by: proc { |li| li.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |li| li.variant.product },
sort_by: proc { |product| product.name },
summary_columns: [ proc { |lis| lis.first.variant.product.supplier.name },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| "" },
proc { |lis| "" },
proc { |lis| lis.sum { |li| li.quantity * (li.variant.weight || 0) } },
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor },
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
{ group_by: proc { |li| li.variant },
sort_by: proc { |variant| variant.full_name } } ]
end
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns
@header = header
@table = order_grouper.table(@line_items)
csv_file_name = "bulk_coop_#{timestamp}.csv"
render_report(@header, @table, params[:csv], csv_file_name)
render_report(@report.header, @table, params[:csv], csv_file_name)
end
def payments
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]).beginning_of_day rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]).end_of_day rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
@search = Spree::Order.complete.not_state(:canceled).managed_by(spree_current_user).search(params[:q])
orders = @search.result
payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments
# -- Prepare Date Params
prepare_date_params params
# -- Prepare Form Options
@distributors = Enterprise.is_distributor.managed_by(spree_current_user)
@report_type = params[:report_type]
case params[:report_type]
when "payments_by_payment_type"
table_items = payments
header = ["Payment State", "Distributor", "Payment Type", "Total (#{currency_symbol})"]
columns = [ proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum { |payment| payment.amount } } ]
rules = [ { group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| Spree::PaymentMethod.unscoped { payment.payment_method } },
sort_by: proc { |method| method.name } } ]
when "itemised_payment_totals"
table_items = orders
header = ["Payment State", "Distributor", "Product Total (#{currency_symbol})", "Shipping Total (#{currency_symbol})", "Outstanding Balance (#{currency_symbol})", "Total (#{currency_symbol})"]
columns = [ proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.sum { |o| o.item_total } },
proc { |orders| orders.sum { |o| o.ship_total } },
proc { |orders| orders.sum { |o| o.outstanding_balance } },
proc { |orders| orders.sum { |o| o.total } } ]
rules = [ { group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } } ]
when "payment_totals"
table_items = orders
header = ["Payment State", "Distributor", "Product Total (#{currency_symbol})", "Shipping Total (#{currency_symbol})", "Total (#{currency_symbol})", "EFT (#{currency_symbol})", "PayPal (#{currency_symbol})", "Outstanding Balance (#{currency_symbol})"]
columns = [ proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.sum { |o| o.item_total } },
proc { |orders| orders.sum { |o| o.ship_total } },
proc { |orders| orders.sum { |o| o.total } },
proc { |orders| orders.sum { |o| o.payments.select { |payment| payment.completed? && (payment.payment_method.name.to_s.include? "EFT") }.sum { |payment| payment.amount } } },
proc { |orders| orders.sum { |o| o.payments.select { |payment| payment.completed? && (payment.payment_method.name.to_s.include? "PayPal") }.sum{ |payment| payment.amount } } },
proc { |orders| orders.sum { |o| o.outstanding_balance } } ]
rules = [ { group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } } ]
else
table_items = payments
header = ["Payment State", "Distributor", "Payment Type", "Total (#{currency_symbol})"]
columns = [ proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum { |payment| payment.amount } } ]
rules = [ { group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| payment.payment_method },
sort_by: proc { |method| method.name } } ]
end
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns
@header = header
@table = order_grouper.table(table_items)
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::PaymentsReport.new spree_current_user, params
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
@table = order_grouper.table(@report.table_items)
csv_file_name = "payments_#{timestamp}.csv"
render_report(@header, @table, params[:csv], csv_file_name)
render_report(@report.header, @table, params[:csv], csv_file_name)
end
def orders_and_fulfillment
# -- Prepare parameters
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]) rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]) rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
# -- Prepare Date Params
prepare_date_params params
# -- Prepare Form Options
permissions = OpenFoodNetwork::Permissions.new(spree_current_user)
# -- Search
@search = Spree::Order.complete.not_state(:canceled).search(params[:q])
orders = permissions.visible_orders.merge(@search.result)
@line_items = permissions.visible_line_items.merge(Spree::LineItem.where(order_id: orders))
@line_items = @line_items.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present?
line_items_with_hidden_details = @line_items.where('"spree_line_items"."id" NOT IN (?)', permissions.editable_line_items)
@line_items.select{ |li| line_items_with_hidden_details.include? li }.each do |line_item|
# TODO We should really be hiding customer code here too, but until we
# have an actual association between order and customer, it's a bit tricky
line_item.order.bill_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil)
line_item.order.ship_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil)
line_item.order.assign_attributes(email: "HIDDEN")
end
# My distributors and any distributors distributing products I supply
@distributors = permissions.visible_enterprises_for_order_reports.is_distributor
# My suppliers and any suppliers supplying products I distribute
@suppliers = permissions.visible_enterprises_for_order_reports.is_primary_producer
@@ -479,233 +198,15 @@ Spree::Admin::ReportsController.class_eval do
@report_types = REPORT_TYPES[:orders_and_fulfillment]
@report_type = params[:report_type]
# -- Format according to report type
case params[:report_type]
when "order_cycle_supplier_totals"
table_items = @line_items
@include_blank = 'All'
@include_blank = 'All'
header = ["Producer", "Product", "Variant", "Amount", "Total Units", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| total_units(line_items) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "" },
proc { |line_items| "incoming transport" } ]
rules = [ { group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
when "order_cycle_supplier_totals_by_distributor"
table_items = @line_items
@include_blank = 'All'
header = ["Producer", "Product", "Variant", "To Hub", "Amount", "Curr. Cost per Unit", "Total Cost", "Shipping Method"]
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "shipping method" } ]
rules = [ { group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name },
summary_columns: [ proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "TOTAL" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "" } ] },
{ group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name } } ]
when "order_cycle_distributor_totals_by_supplier"
table_items = @line_items
@include_blank = 'All'
header = ["Hub", "Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Total Shipping Cost", "Shipping Method"]
columns = [ proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "" },
proc { |line_items| "shipping method" } ]
rules = [ { group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name },
summary_columns: [ proc { |line_items| "" },
proc { |line_items| "TOTAL" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
proc { |line_items| "" } ] },
{ group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
when "order_cycle_customer_totals"
table_items = @line_items
@include_blank = 'All'
header = ["Hub", "Customer", "Email", "Phone", "Producer", "Product", "Variant",
"Amount", "Item (#{currency_symbol})", "Item + Fees (#{currency_symbol})", "Admin & Handling (#{currency_symbol})", "Ship (#{currency_symbol})", "Total (#{currency_symbol})", "Paid?",
"Shipping", "Delivery?",
"Ship Street", "Ship Street 2", "Ship City", "Ship Postcode", "Ship State",
"Comments", "SKU",
"Order Cycle", "Payment Method", "Customer Code", "Tags",
"Billing Street 1", "Billing Street 2", "Billing City", "Billing Postcode", "Billing State"
]
rsa = proc { |line_items| line_items.first.order.shipping_method.andand.require_ship_address }
columns = [
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.order.bill_address.firstname + " " + line_items.first.order.bill_address.lastname },
proc { |line_items| line_items.first.order.email },
proc { |line_items| line_items.first.order.bill_address.phone },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.first.order.shipping_method.andand.name },
proc { |line_items| rsa.call(line_items) ? 'Y' : 'N' },
proc { |line_items| line_items.first.order.ship_address.andand.address1 if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.address2 if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.city if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.zipcode if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.state if rsa.call(line_items) },
proc { |line_items| "" },
proc { |line_items| line_items.first.variant.product.sku },
proc { |line_items| line_items.first.order.order_cycle.andand.name },
proc { |line_items| line_items.first.order.payments.first.andand.payment_method.andand.name },
proc { |line_items| line_items.first.order.user.andand.customer_of(line_items.first.order.distributor).andand.code },
proc { |line_items| "" },
proc { |line_items| line_items.first.order.bill_address.andand.address1 },
proc { |line_items| line_items.first.order.bill_address.andand.address2 },
proc { |line_items| line_items.first.order.bill_address.andand.city },
proc { |line_items| line_items.first.order.bill_address.andand.zipcode },
proc { |line_items| line_items.first.order.bill_address.andand.state } ]
rules = [ { group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.bill_address.lastname + " " + order.bill_address.firstname },
summary_columns: [
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.order.bill_address.firstname + " " + line_items.first.order.bill_address.lastname },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "TOTAL" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.admin_and_handling_total } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.total } },
proc { |line_items| line_items.all? { |li| li.order.paid? } ? "Yes" : "No" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.first.order.special_instructions } ,
proc { |line_items| "" },
proc { |line_items| line_items.first.order.order_cycle.andand.name },
proc { |line_items| line_items.first.order.payments.first.andand.payment_method.andand.name },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" }
] },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
else
table_items = @line_items
@include_blank = 'All'
header = ["Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
proc { |line_items| "" },
proc { |line_items| "incoming transport" } ]
rules = [ { group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
end
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns
@header = header
@table = order_grouper.table(table_items)
# -- Build Report with Order Grouper
@report = OpenFoodNetwork::OrdersAndFulfillmentsReport.new spree_current_user, params
order_grouper = OpenFoodNetwork::OrderGrouper.new @report.rules, @report.columns
@table = order_grouper.table(@report.table_items)
csv_file_name = "#{params[:report_type]}_#{timestamp}.csv"
render_report(@header, @table, params[:csv], csv_file_name)
render_report(@report.header, @table, params[:csv], csv_file_name)
end
@@ -750,6 +251,20 @@ Spree::Admin::ReportsController.class_eval do
private
def prepare_date_params(params)
# -- Prepare parameters
params[:q] ||= {}
if params[:q][:completed_at_gt].blank?
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
else
params[:q][:completed_at_gt] = Time.zone.parse(params[:q][:completed_at_gt]) rescue Time.zone.now.beginning_of_month
end
if params[:q] && !params[:q][:completed_at_lt].blank?
params[:q][:completed_at_lt] = Time.zone.parse(params[:q][:completed_at_lt]) rescue ""
end
params[:q][:meta_sort] ||= "completed_at.desc"
end
def load_data
# Load distributors either owned by the user or selling their enterprises products.
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
@@ -781,15 +296,6 @@ Spree::Admin::ReportsController.class_eval do
reports.select { |action| can? action, :report }
end
def total_units(line_items)
return " " if line_items.map{ |li| li.variant.unit_value.nil? }.any?
total_units = line_items.sum do |li|
scale_factor = ( li.product.variant_unit == 'weight' ? 1000 : 1 )
li.quantity * li.variant.unit_value / scale_factor
end
total_units.round(3)
end
def timestamp
Time.now.strftime("%Y%m%d")
end

View File

@@ -1,4 +1,4 @@
= form_for @search, :url => spree.bulk_coop_admin_reports_path do |f|
= form_for @report.search, :url => spree.bulk_coop_admin_reports_path do |f|
= render 'date_range_form', f: f
.row
@@ -20,7 +20,7 @@
%table#listing_orders.index
%thead
%tr{'data-hook' => "orders_header"}
- @header.each do |heading|
- @report.header.each do |heading|
%th=heading
%tbody
- @table.each do |row|

View File

@@ -1,4 +1,4 @@
= form_for @search, :url => spree.orders_and_fulfillment_admin_reports_path do |f|
= form_for @report.search, :url => spree.orders_and_fulfillment_admin_reports_path do |f|
= render 'date_range_form', f: f
.row
@@ -30,7 +30,7 @@
%table#listing_orders.index
%thead
%tr{'data-hook' => "orders_header"}
- @header.each do |heading|
- @report.header.each do |heading|
%th=heading
%tbody
- @table.each do |row|

View File

@@ -1,4 +1,4 @@
= form_for @search, :url => spree.payments_admin_reports_path do |f|
= form_for @report.search, :url => spree.payments_admin_reports_path do |f|
= render 'date_range_form', f: f
.row
@@ -20,7 +20,7 @@
%table#listing_orders.index
%thead
%tr{'data-hook' => "orders_header"}
- @header.each do |heading|
- @report.header.each do |heading|
%th=heading
%tbody
- @table.each do |row|

View File

@@ -0,0 +1,104 @@
require 'open_food_network/reports/bulk_coop_supplier_report'
require 'open_food_network/reports/bulk_coop_allocation_report'
module OpenFoodNetwork
class BulkCoopReport
attr_reader :params
def initialize(user, params = {})
@params = params
@user = user
@supplier_report = OpenFoodNetwork::Reports::BulkCoopSupplierReport.new
@allocation_report = OpenFoodNetwork::Reports::BulkCoopAllocationReport.new
end
def header
case params[:report_type]
when "bulk_coop_supplier_report"
@supplier_report.header
when "bulk_coop_allocation"
@allocation_report.header
when "bulk_coop_packing_sheets"
["Customer", "Product", "Variant", "Sum Total"]
when "bulk_coop_customer_payments"
["Customer", "Date of Order", "Total Cost", "Amount Owing", "Amount Paid"]
else
["Supplier", "Product", "Unit Size", "Variant", "Weight", "Sum Total", "Sum Max Total", "Units Required", "Remainder"]
end
end
def search
Spree::Order.complete.not_state(:canceled).managed_by(@user).search(params[:q])
end
def table_items
orders = search.result
orders.map { |o| o.line_items.managed_by(@user) }.flatten
end
def rules
case params[:report_type]
when "bulk_coop_supplier_report"
@supplier_report.rules
when "bulk_coop_allocation"
@allocation_report.rules
when "bulk_coop_packing_sheets"
[ { group_by: proc { |li| li.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |li| li.variant },
sort_by: proc { |variant| variant.full_name } },
{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.to_s } } ]
when "bulk_coop_customer_payments"
[ { group_by: proc { |li| li.order },
sort_by: proc { |order| order.completed_at } } ]
else
[ { group_by: proc { |li| li.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |li| li.variant.product },
sort_by: proc { |product| product.name },
summary_columns: [ proc { |lis| lis.first.variant.product.supplier.name },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| "" },
proc { |lis| "" },
proc { |lis| lis.sum { |li| li.quantity * (li.variant.weight || 0) } },
proc { |lis| lis.sum { |li| (li.max_quantity || 0) * (li.variant.weight || 0) } },
proc { |lis| ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor },
proc { |lis| lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } - ( ( (lis.first.variant.product.group_buy_unit_size || 0).zero? ? 0 : ( lis.sum { |li| ( [li.max_quantity || 0, li.quantity || 0].max ) * (li.variant.weight || 0) } / lis.first.variant.product.group_buy_unit_size ) ).floor * (lis.first.variant.product.group_buy_unit_size || 0) ) } ] },
{ group_by: proc { |li| li.variant },
sort_by: proc { |variant| variant.full_name } } ]
end
end
def columns
case params[:report_type]
when "bulk_coop_supplier_report"
@supplier_report.columns
when "bulk_coop_allocation"
@allocation_report.columns
when "bulk_coop_packing_sheets"
[ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.full_name },
proc { |lis| lis.sum { |li| li.quantity } } ]
when "bulk_coop_customer_payments"
[ proc { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname },
proc { |lis| lis.first.order.completed_at.to_s },
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.total } },
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.outstanding_balance } },
proc { |lis| lis.map { |li| li.order }.uniq.sum { |o| o.payment_total } } ]
else
[ proc { |lis| lis.first.variant.product.supplier.name },
proc { |lis| lis.first.variant.product.name },
proc { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" },
proc { |lis| lis.first.variant.full_name },
proc { |lis| lis.first.variant.weight || 0 },
proc { |lis| lis.sum { |li| li.quantity } },
proc { |lis| lis.sum { |li| li.max_quantity || 0 } },
proc { |lis| "" },
proc { |lis| "" } ]
end
end
end
end

View File

@@ -15,6 +15,17 @@ module OpenFoodNetwork
name_fields.join ' '
end
def value
value, _ = option_value_value_unit
value
end
def unit
_, unit = option_value_value_unit
unit
end
private
def value_scaled?

View File

@@ -8,7 +8,7 @@ module OpenFoodNetwork
def build_tree(items, remaining_rules)
rules = remaining_rules.clone
unless rules.empty?
if rules.any?
rule = rules.delete_at(0) # Remove current rule for subsequent groupings
group_and_sort(rule, rules, items)
else
@@ -20,7 +20,7 @@ module OpenFoodNetwork
branch = {}
groups = items.group_by { |item| rule[:group_by].call(item) }
sorted_groups = groups.sort_by { |key, value| rule[:sort_by].call(key) }
sorted_groups.each do |property,items_by_property|
sorted_groups.each do |property, items_by_property|
branch[property] = build_tree(items_by_property, remaining_rules)
branch[property][:summary_row] = { items: items_by_property, columns: rule[:summary_columns] } unless rule[:summary_columns] == nil || is_leaf_node(branch[property])
end
@@ -44,15 +44,15 @@ module OpenFoodNetwork
end
def table(items)
tree = build_tree(items,@rules)
tree = build_tree(items, @rules)
table = build_table(tree)
table
end
private
def is_leaf_node(node)
node.is_a? Array
end
end
end
end

View File

@@ -0,0 +1,256 @@
include Spree::ReportsHelper
module OpenFoodNetwork
class OrdersAndFulfillmentsReport
attr_reader :params
def initialize(user, params = {})
@params = params
@user = user
end
def header
case params[:report_type]
when "order_cycle_supplier_totals"
["Producer", "Product", "Variant", "Amount", "Total Units", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
when "order_cycle_supplier_totals_by_distributor"
["Producer", "Product", "Variant", "To Hub", "Amount", "Curr. Cost per Unit", "Total Cost", "Shipping Method"]
when "order_cycle_distributor_totals_by_supplier"
["Hub", "Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Total Shipping Cost", "Shipping Method"]
when "order_cycle_customer_totals"
["Hub", "Customer", "Email", "Phone", "Producer", "Product", "Variant",
"Amount", "Item (#{currency_symbol})", "Item + Fees (#{currency_symbol})", "Admin & Handling (#{currency_symbol})", "Ship (#{currency_symbol})", "Total (#{currency_symbol})", "Paid?",
"Shipping", "Delivery?",
"Ship Street", "Ship Street 2", "Ship City", "Ship Postcode", "Ship State",
"Comments", "SKU",
"Order Cycle", "Payment Method", "Customer Code", "Tags",
"Billing Street 1", "Billing Street 2", "Billing City", "Billing Postcode", "Billing State"
]
else
["Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
end
end
def search
Spree::Order.complete.not_state(:canceled).search(params[:q])
end
def table_items
permissions = OpenFoodNetwork::Permissions.new(@user)
orders = permissions.visible_orders.merge(search.result)
line_items = permissions.visible_line_items.merge(Spree::LineItem.where(order_id: orders))
line_items = line_items.supplied_by_any(params[:supplier_id_in]) if params[:supplier_id_in].present?
line_items_with_hidden_details = line_items.where('"spree_line_items"."id" NOT IN (?)', permissions.editable_line_items)
line_items.select{ |li| line_items_with_hidden_details.include? li }.each do |line_item|
# TODO We should really be hiding customer code here too, but until we
# have an actual association between order and customer, it's a bit tricky
line_item.order.bill_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil)
line_item.order.ship_address.assign_attributes(firstname: "HIDDEN", lastname: "", phone: "", address1: "", address2: "", city: "", zipcode: "", state: nil)
line_item.order.assign_attributes(email: "HIDDEN")
end
line_items
end
def rules
case params[:report_type]
when "order_cycle_supplier_totals"
[ { group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
when "order_cycle_supplier_totals_by_distributor"
[ { group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name },
summary_columns: [ proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "TOTAL" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "" } ] },
{ group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name } } ]
when "order_cycle_distributor_totals_by_supplier"
[ { group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name },
summary_columns: [ proc { |line_items| "" },
proc { |line_items| "TOTAL" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
proc { |line_items| "" } ] },
{ group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
when "order_cycle_customer_totals"
[ { group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.bill_address.lastname + " " + order.bill_address.firstname },
summary_columns: [
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.order.bill_address.firstname + " " + line_items.first.order.bill_address.lastname },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "TOTAL" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.admin_and_handling_total } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.total } },
proc { |line_items| line_items.all? { |li| li.order.paid? } ? "Yes" : "No" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.first.order.special_instructions } ,
proc { |line_items| "" },
proc { |line_items| line_items.first.order.order_cycle.andand.name },
proc { |line_items| line_items.first.order.payments.first.andand.payment_method.andand.name },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" }
] },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
else
[ { group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name } } ]
end
end
def columns
case params[:report_type]
when "order_cycle_supplier_totals"
[ proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| total_units(line_items) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "" },
proc { |line_items| "incoming transport" } ]
when "order_cycle_supplier_totals_by_distributor"
[ proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "shipping method" } ]
when "order_cycle_distributor_totals_by_supplier"
[ proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| "" },
proc { |line_items| "shipping method" } ]
when "order_cycle_customer_totals"
rsa = proc { |line_items| line_items.first.order.shipping_method.andand.require_ship_address }
[
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.order.bill_address.firstname + " " + line_items.first.order.bill_address.lastname },
proc { |line_items| line_items.first.order.email },
proc { |line_items| line_items.first.order.bill_address.phone },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.sum { |li| li.amount } },
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| "" },
proc { |line_items| line_items.first.order.shipping_method.andand.name },
proc { |line_items| rsa.call(line_items) ? 'Y' : 'N' },
proc { |line_items| line_items.first.order.ship_address.andand.address1 if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.address2 if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.city if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.zipcode if rsa.call(line_items) },
proc { |line_items| line_items.first.order.ship_address.andand.state if rsa.call(line_items) },
proc { |line_items| "" },
proc { |line_items| line_items.first.variant.product.sku },
proc { |line_items| line_items.first.order.order_cycle.andand.name },
proc { |line_items| line_items.first.order.payments.first.andand.payment_method.andand.name },
proc { |line_items| line_items.first.order.user.andand.customer_of(line_items.first.order.distributor).andand.code },
proc { |line_items| "" },
proc { |line_items| line_items.first.order.bill_address.andand.address1 },
proc { |line_items| line_items.first.order.bill_address.andand.address2 },
proc { |line_items| line_items.first.order.bill_address.andand.city },
proc { |line_items| line_items.first.order.bill_address.andand.zipcode },
proc { |line_items| line_items.first.order.bill_address.andand.state } ]
else
[ proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.sum { |li| li.quantity } },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
proc { |line_items| "" },
proc { |line_items| "incoming transport" } ]
end
end
private
def total_units(line_items)
return " " if line_items.map{ |li| li.variant.unit_value.nil? }.any?
total_units = line_items.sum do |li|
scale_factor = ( li.product.variant_unit == 'weight' ? 1000 : 1 )
li.quantity * li.variant.unit_value / scale_factor
end
total_units.round(3)
end
end
end

View File

@@ -0,0 +1,101 @@
module OpenFoodNetwork
class PaymentsReport
attr_reader :params
def initialize(user, params = {})
@params = params
@user = user
end
def header
case params[:report_type]
when "payments_by_payment_type"
["Payment State", "Distributor", "Payment Type", "Total (#{currency_symbol})"]
when "itemised_payment_totals"
["Payment State", "Distributor", "Product Total (#{currency_symbol})", "Shipping Total (#{currency_symbol})", "Outstanding Balance (#{currency_symbol})", "Total (#{currency_symbol})"]
when "payment_totals"
["Payment State", "Distributor", "Product Total (#{currency_symbol})", "Shipping Total (#{currency_symbol})", "Total (#{currency_symbol})", "EFT (#{currency_symbol})", "PayPal (#{currency_symbol})", "Outstanding Balance (#{currency_symbol})"]
else
["Payment State", "Distributor", "Payment Type", "Total (#{currency_symbol})"]
end
end
def search
Spree::Order.complete.not_state(:canceled).managed_by(@user).search(params[:q])
end
def table_items
orders = search.result
payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments
case params[:report_type]
when "payments_by_payment_type"
payments
when "itemised_payment_totals"
orders
when "payment_totals"
orders
else
payments
end
end
def rules
case params[:report_type]
when "payments_by_payment_type"
[ { group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| Spree::PaymentMethod.unscoped { payment.payment_method } },
sort_by: proc { |method| method.name } } ]
when "itemised_payment_totals"
[ { group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } } ]
when "payment_totals"
[ { group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } } ]
else
[ { group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| payment.payment_method },
sort_by: proc { |method| method.name } } ]
end
end
def columns
case params[:report_type]
when "payments_by_payment_type"
[ proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum { |payment| payment.amount } } ]
when "itemised_payment_totals"
[ proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.sum { |o| o.item_total } },
proc { |orders| orders.sum { |o| o.ship_total } },
proc { |orders| orders.sum { |o| o.outstanding_balance } },
proc { |orders| orders.sum { |o| o.total } } ]
when "payment_totals"
[ proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.sum { |o| o.item_total } },
proc { |orders| orders.sum { |o| o.ship_total } },
proc { |orders| orders.sum { |o| o.total } },
proc { |orders| orders.sum { |o| o.payments.select { |payment| payment.completed? && (payment.payment_method.name.to_s.include? "EFT") }.sum { |payment| payment.amount } } },
proc { |orders| orders.sum { |o| o.payments.select { |payment| payment.completed? && (payment.payment_method.name.to_s.include? "PayPal") }.sum{ |payment| payment.amount } } },
proc { |orders| orders.sum { |o| o.outstanding_balance } } ]
else
[ proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum { |payment| payment.amount } } ]
end
end
end
end

View File

@@ -0,0 +1,50 @@
require 'open_food_network/reports/bulk_coop_report'
module OpenFoodNetwork::Reports
class BulkCoopAllocationReport < BulkCoopReport
header "Customer", "Product", "Unit Size", "Variant", "Variant value", "Variant unit", "Weight", "Sum Total", "Total Available", "Unallocated", "Max quantity excess"
organise do
group { |li| li.variant.product }
sort &:name
summary_row do
column { |lis| "TOTAL" }
column { |lis| product_name(lis) }
column { |lis| group_buy_unit_size_f(lis) }
column { |lis| "" }
column { |lis| "" }
column { |lis| "" }
column { |lis| "" }
column { |lis| total_amount(lis) }
column { |lis| total_available(lis) }
column { |lis| remainder(lis) }
column { |lis| max_quantity_excess(lis) }
end
organise do
group { |li| li.variant }
sort &:full_name
organise do
group { |li| li.order }
sort { |order| order.to_s }
end
end
end
columns do
column { |lis| lis.first.order.bill_address.firstname + " " + lis.first.order.bill_address.lastname }
column { |lis| lis.first.variant.product.name }
column { |lis| lis.first.variant.product.group_buy ? (lis.first.variant.product.group_buy_unit_size || 0.0) : "" }
column { |lis| lis.first.variant.full_name }
column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first.variant).value }
column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first.variant).unit }
column { |lis| lis.first.variant.weight || 0 }
column { |lis| total_amount(lis) }
column { |lis| "" }
column { |lis| "" }
column { |lis| "" }
end
end
end

View File

@@ -0,0 +1,70 @@
require 'open_food_network/reports/report'
module OpenFoodNetwork::Reports
class BulkCoopReport < Report
private
class << self
def supplier_name(lis)
lis.first.variant.product.supplier.name
end
def product_name(lis)
lis.first.variant.product.name
end
def group_buy_unit_size(lis)
(lis.first.variant.product.group_buy_unit_size || 0.0) /
(lis.first.product.variant_unit_scale || 1)
end
def group_buy_unit_size_f(lis)
if lis.first.variant.product.group_buy
group_buy_unit_size(lis)
else
""
end
end
def total_amount(lis)
lis.sum { |li| (li.quantity || 0) * scaled_amount(li) }
end
def units_required(lis)
if group_buy_unit_size(lis).zero?
0
else
( total_amount(lis) / group_buy_unit_size(lis) ).ceil
end
end
def total_available(lis)
units_required(lis) * group_buy_unit_size(lis)
end
def remainder(lis)
remainder = total_available(lis) - total_amount(lis)
remainder >= 0 ? remainder : ''
end
def max_quantity_excess(lis)
max_quantity_amount(lis) - total_amount(lis)
end
def max_quantity_amount(lis)
lis.sum do |li|
max_quantity = [li.max_quantity || 0, li.quantity || 0].max
max_quantity * scaled_amount(li)
end
end
def scaled_amount(li)
(li.variant.unit_value || 0) / (li.product.variant_unit_scale || 1)
end
end
end
end

View File

@@ -0,0 +1,50 @@
require 'open_food_network/reports/bulk_coop_report'
module OpenFoodNetwork::Reports
class BulkCoopSupplierReport < BulkCoopReport
header "Supplier", "Product", "Unit Size", "Variant", "Variant value", "Variant unit", "Weight", "Sum Total", "Units Required", "Unallocated", "Max quantity excess"
organise do
group { |li| li.variant.product.supplier }
sort &:name
organise do
group { |li| li.variant.product }
sort &:name
summary_row do
column { |lis| supplier_name(lis) }
column { |lis| product_name(lis) }
column { |lis| group_buy_unit_size_f(lis) }
column { |lis| "" }
column { |lis| "" }
column { |lis| "" }
column { |lis| "" }
column { |lis| total_amount(lis) }
column { |lis| units_required(lis) }
column { |lis| remainder(lis) }
column { |lis| max_quantity_excess(lis) }
end
organise do
group { |li| li.variant }
sort &:full_name
end
end
end
columns do
column { |lis| supplier_name(lis) }
column { |lis| product_name(lis) }
column { |lis| group_buy_unit_size_f(lis) }
column { |lis| lis.first.variant.full_name }
column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first.variant).value }
column { |lis| OpenFoodNetwork::OptionValueNamer.new(lis.first.variant).unit }
column { |lis| lis.first.variant.weight || 0 }
column { |lis| total_amount(lis) }
column { |lis| '' }
column { |lis| '' }
column { |lis| '' }
end
end
end

View File

@@ -0,0 +1,46 @@
require 'open_food_network/reports/row'
require 'open_food_network/reports/rule'
module OpenFoodNetwork::Reports
class Report
class_attribute :_header, :_columns, :_rules_head
# -- API
def header
self._header
end
def columns
self._columns.to_a
end
def rules
# Flatten linked list and return as hashes
rules = []
rule = self._rules_head
while rule
rules << rule
rule = rule.next
end
rules.map &:to_h
end
# -- DSL
def self.header(*columns)
self._header = columns
end
def self.columns(&block)
self._columns = Row.new
Blockenspiel.invoke block, self._columns
end
def self.organise(&block)
self._rules_head = Rule.new
Blockenspiel.invoke block, self._rules_head
end
end
end

View File

@@ -0,0 +1,17 @@
module OpenFoodNetwork::Reports
class Row
include Blockenspiel::DSL
def initialize
@columns = []
end
def column(&block)
@columns << block
end
def to_a
@columns
end
end
end

View File

@@ -0,0 +1,32 @@
require 'open_food_network/reports/row'
module OpenFoodNetwork::Reports
class Rule
include Blockenspiel::DSL
attr_reader :next
def group(&block)
@group = block
end
def sort(&block)
@sort = block
end
def summary_row(&block)
@summary_row = Row.new
Blockenspiel.invoke block, @summary_row
end
def organise(&block)
@next = Rule.new
Blockenspiel.invoke block, @next
end
def to_h
h = {group_by: @group, sort_by: @sort}
h.merge!({summary_columns: @summary_row.to_a}) if @summary_row
h
end
end
end

View File

@@ -56,6 +56,11 @@ describe Spree::Admin::ReportsController do
order
end
# Results
let(:resulting_orders_prelim) { assigns(:report).search.result }
let(:resulting_orders) { assigns(:report).table_items.map(&:order) }
let(:resulting_products) { assigns(:report).table_items.map(&:product) }
# As manager of a coordinator (c1)
context "Coordinator Enterprise User" do
before { login_as_enterprise_user [c1] }
@@ -64,8 +69,8 @@ describe Spree::Admin::ReportsController do
it "shows all orders in order cycles I coordinate" do
spree_get :orders_and_fulfillment
assigns(:line_items).map(&:order).should include orderA1, orderA2
assigns(:line_items).map(&:order).should_not include orderB1, orderB2
resulting_orders.should include orderA1, orderA2
resulting_orders.should_not include orderB1, orderB2
end
end
end
@@ -88,9 +93,9 @@ describe Spree::Admin::ReportsController do
it "only shows orders that I have access to" do
spree_get :bulk_coop
assigns(:search).result.should include(orderA1, orderB1)
assigns(:search).result.should_not include(orderA2)
assigns(:search).result.should_not include(orderB2)
resulting_orders.should include(orderA1, orderB1)
resulting_orders.should_not include(orderA2)
resulting_orders.should_not include(orderB2)
end
end
@@ -98,9 +103,9 @@ describe Spree::Admin::ReportsController do
it "only shows orders that I have access to" do
spree_get :payments
assigns(:search).result.should include(orderA1, orderB1)
assigns(:search).result.should_not include(orderA2)
assigns(:search).result.should_not include(orderB2)
resulting_orders_prelim.should include(orderA1, orderB1)
resulting_orders_prelim.should_not include(orderA2)
resulting_orders_prelim.should_not include(orderB2)
end
end
@@ -108,15 +113,15 @@ describe Spree::Admin::ReportsController do
it "only shows orders that I distribute" do
spree_get :orders_and_fulfillment
assigns(:line_items).map(&:order).should include orderA1, orderB1
assigns(:line_items).map(&:order).should_not include orderA2, orderB2
resulting_orders.should include orderA1, orderB1
resulting_orders.should_not include orderA2, orderB2
end
it "only shows the selected order cycle" do
spree_get :orders_and_fulfillment, q: {order_cycle_id_in: [ocA.id.to_s]}
assigns(:search).result.should include(orderA1)
assigns(:search).result.should_not include(orderB1)
resulting_orders.should include(orderA1)
resulting_orders.should_not include(orderB1)
end
end
end
@@ -129,8 +134,8 @@ describe Spree::Admin::ReportsController do
it "only shows product line items that I am supplying" do
spree_get :bulk_coop
assigns(:line_items).map(&:product).should include p1
assigns(:line_items).map(&:product).should_not include p2, p3
resulting_products.should include p1
resulting_products.should_not include p2, p3
end
end
@@ -143,8 +148,8 @@ describe Spree::Admin::ReportsController do
it "only shows product line items that I am supplying" do
spree_get :orders_and_fulfillment
assigns(:line_items).map(&:product).should include p1
assigns(:line_items).map(&:product).should_not include p2, p3
resulting_products.should include p1
resulting_products.should_not include p2, p3
end
end
@@ -152,15 +157,15 @@ describe Spree::Admin::ReportsController do
it "does not show me line_items I supply" do
spree_get :orders_and_fulfillment
assigns(:line_items).map(&:product).should_not include p1, p2, p3
resulting_products.should_not include p1, p2, p3
end
end
it "only shows the selected order cycle" do
spree_get :orders_and_fulfillment, q: {order_cycle_id_eq: ocA.id}
assigns(:search).result.should include(orderA1)
assigns(:search).result.should_not include(orderB1)
resulting_orders_prelim.should include(orderA1)
resulting_orders_prelim.should_not include(orderB1)
end
end
end

View File

@@ -10,6 +10,8 @@ feature %q{
let!(:invisible_producer) { create(:supplier_enterprise, visible: false) }
let(:taxon) { create(:taxon) }
let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) }
let(:shop) { create(:distributor_enterprise) }
let!(:er) { create(:enterprise_relationship, parent: shop, child: producer) }
before do
visit producers_path
@@ -24,4 +26,9 @@ feature %q{
it "doesn't show invisible producers" do
page.should_not have_content invisible_producer.name
end
it "links to places to buy produce" do
expand_active_table_node producer.name
page.should have_link shop.name
end
end

View File

@@ -0,0 +1,100 @@
require 'open_food_network/reports/report'
module OpenFoodNetwork::Reports
P1 = Proc.new { |o| o[:one] }
P2 = Proc.new { |o| o[:two] }
P3 = Proc.new { |o| o[:three] }
P4 = Proc.new { |o| o[:four] }
class TestReport < Report
header 'One', 'Two', 'Three', 'Four'
columns do
column &P1
column &P2
column &P3
column &P4
end
organise do
group &P1
sort &P2
organise do
group &P3
sort &P4
summary_row do
column &P1
column &P4
end
end
end
end
class HelperReport < Report
columns do
column { |o| my_helper(o) }
end
private
def self.my_helper(o)
o[:one]
end
end
describe Report do
let(:report) { TestReport.new }
let(:helper_report) { HelperReport.new }
let(:rules_head) { TestReport._rules_head }
let(:data) { {one: 1, two: 2, three: 3, four: 4} }
it "returns the header" do
report.header.should == %w(One Two Three Four)
end
it "returns columns as an array of procs" do
report.columns[0].call(data).should == 1
report.columns[1].call(data).should == 2
report.columns[2].call(data).should == 3
report.columns[3].call(data).should == 4
end
it "supports helpers when outputting columns" do
helper_report.columns[0].call(data).should == 1
end
describe "rules" do
let(:group_by) { rules_head.to_h[:group_by] }
let(:sort_by) { rules_head.to_h[:sort_by] }
let(:next_group_by) { rules_head.next.to_h[:group_by] }
let(:next_sort_by) { rules_head.next.to_h[:sort_by] }
let(:next_summary_columns) { rules_head.next.to_h[:summary_columns] }
it "constructs the head of the rules list" do
group_by.call(data).should == 1
sort_by.call(data).should == 2
end
it "constructs nested rules" do
next_group_by.call(data).should == 3
next_sort_by.call(data).should == 4
end
it "constructs summary columns for rules" do
next_summary_columns[0].call(data).should == 1
next_summary_columns[1].call(data).should == 4
end
end
describe "outputting rules" do
it "outputs the rules" do
report.rules.should == [{group_by: P1, sort_by: P2},
{group_by: P3, sort_by: P4, summary_columns: [P1, P4]}]
end
end
end
end

View File

@@ -0,0 +1,16 @@
require 'open_food_network/reports/row'
module OpenFoodNetwork::Reports
describe Row do
let(:row) { Row.new }
let(:proc) { Proc.new {} }
it "can define a number of columns and return them as an array" do
row.column &proc
row.column &proc
row.column &proc
row.to_a.should == [proc, proc, proc]
end
end
end

View File

@@ -0,0 +1,36 @@
require 'open_food_network/reports/rule'
module OpenFoodNetwork::Reports
describe Rule do
let(:rule) { Rule.new }
let(:proc) { Proc.new {} }
it "can define a group proc and return it in a hash" do
rule.group &proc
rule.to_h.should == {group_by: proc, sort_by: nil}
end
it "can define a sort proc and return it in a hash" do
rule.sort &proc
rule.to_h.should == {group_by: nil, sort_by: proc}
end
it "can define a nested rule" do
rule.organise &proc
rule.next.should be_a Rule
end
it "can define a summary row and return it in a hash" do
rule.summary_row do
column {}
column {}
column {}
end
rule.to_h[:summary_columns].count.should == 3
rule.to_h[:summary_columns][0].should be_a Proc
rule.to_h[:summary_columns][1].should be_a Proc
rule.to_h[:summary_columns][2].should be_a Proc
end
end
end