mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge remote-tracking branch 'origin/xero-report' into combined/xero-report_show-order-without-distributor
This commit is contained in:
@@ -7,6 +7,7 @@ require 'open_food_network/customers_report'
|
||||
require 'open_food_network/users_and_enterprises_report'
|
||||
require 'open_food_network/order_cycle_management_report'
|
||||
require 'open_food_network/sales_tax_report'
|
||||
require 'open_food_network/xero_invoices_report'
|
||||
|
||||
Spree::Admin::ReportsController.class_eval do
|
||||
|
||||
@@ -679,7 +680,22 @@ Spree::Admin::ReportsController.class_eval do
|
||||
render_report(@report.header, @report.table, params[:csv], "users_and_enterprises_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
def render_report (header, table, create_csv, csv_file_name)
|
||||
def xero_invoices
|
||||
if request.get?
|
||||
params[:q] ||= {}
|
||||
params[:q][:completed_at_gt] = Time.zone.now.beginning_of_month
|
||||
end
|
||||
@distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
|
||||
|
||||
@search = Spree::Order.complete.managed_by(spree_current_user).order('id DESC').search(params[:q])
|
||||
orders = @search.result
|
||||
@report = OpenFoodNetwork::XeroInvoicesReport.new orders, params
|
||||
render_report(@report.header, @report.table, params[:csv], "xero_invoices_#{timestamp}.csv")
|
||||
end
|
||||
|
||||
|
||||
def render_report(header, table, create_csv, csv_file_name)
|
||||
unless create_csv
|
||||
render :html => table
|
||||
else
|
||||
@@ -716,7 +732,9 @@ Spree::Admin::ReportsController.class_eval do
|
||||
: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 => ''},
|
||||
:sales_tax => { :name => "Sales Tax", :description => "Sales Tax For Orders" }
|
||||
:sales_tax => { :name => "Sales Tax", :description => "Sales Tax For Orders" },
|
||||
:xero_invoices => { :name => "Xero Invoices", :description => 'Invoices for import into Xero' }
|
||||
|
||||
}
|
||||
# Return only reports the user is authorized to view.
|
||||
reports.select { |action| can? action, :report }
|
||||
|
||||
@@ -4,6 +4,8 @@ module Spree
|
||||
|
||||
scope :enterprise_fee, where(originator_type: 'EnterpriseFee')
|
||||
scope :included_tax, where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::LineItem')
|
||||
scope :with_tax, where('spree_adjustments.included_tax > 0')
|
||||
scope :without_tax, where('spree_adjustments.included_tax = 0')
|
||||
|
||||
attr_accessible :included_tax
|
||||
|
||||
|
||||
@@ -24,6 +24,15 @@ Spree::LineItem.class_eval do
|
||||
where('spree_products.supplier_id IN (?)', enterprises)
|
||||
}
|
||||
|
||||
scope :with_tax, joins(:adjustments).
|
||||
where('spree_adjustments.originator_type = ?', 'Spree::TaxRate').
|
||||
select('DISTINCT spree_line_items.*')
|
||||
|
||||
# Line items without a Spree::TaxRate-originated adjustment
|
||||
scope :without_tax, joins("LEFT OUTER JOIN spree_adjustments ON (spree_adjustments.adjustable_id=spree_line_items.id AND spree_adjustments.adjustable_type = 'Spree::LineItem' AND spree_adjustments.originator_type='Spree::TaxRate')").
|
||||
where('spree_adjustments.id IS NULL')
|
||||
|
||||
|
||||
def price_with_adjustments
|
||||
# EnterpriseFee#create_locked_adjustment applies adjustments on line items to their parent order,
|
||||
# so line_item.adjustments returns an empty array
|
||||
|
||||
44
app/views/spree/admin/reports/xero_invoices.html.haml
Normal file
44
app/views/spree/admin/reports/xero_invoices.html.haml
Normal file
@@ -0,0 +1,44 @@
|
||||
= form_for @search, url: spree.xero_invoices_admin_reports_path do |f|
|
||||
= render 'date_range_form', f: f
|
||||
|
||||
.row
|
||||
.four.columns.alpha= label_tag nil, "Hub: "
|
||||
.four.columns.omega= f.collection_select(:distributor_id_eq, @distributors, :id, :name, {:include_blank => 'All'}, {:class => "select2 fullwidth"})
|
||||
.row
|
||||
.four.columns.alpha= label_tag nil, "Order Cycle: "
|
||||
.four.columns.omega= f.select(:order_cycle_id_eq,
|
||||
options_for_select(report_order_cycle_options(@order_cycles), params[:q][:order_cycle_id_eq]),
|
||||
{:include_blank => true}, {:class => "select2 fullwidth"})
|
||||
|
||||
.row
|
||||
.four.columns.alpha= label_tag :initial_invoice_number, "Initial invoice number:"
|
||||
.twelve.columns.omega= text_field_tag :initial_invoice_number, params[:initial_invoice_number]
|
||||
.row
|
||||
.four.columns.alpha= label_tag :invoice_date, "Invoice date:"
|
||||
.twelve.columns.omega= text_field_tag :invoice_date, params[:invoice_date], class: 'datetimepicker'
|
||||
.row
|
||||
.four.columns.alpha= label_tag :due_date, "Due date:"
|
||||
.twelve.columns.omega= text_field_tag :due_date, params[:due_date], class: 'datetimepicker'
|
||||
.row
|
||||
.four.columns.alpha= label_tag :account_code, "Account code:"
|
||||
.twelve.columns.omega= text_field_tag :account_code, params[:account_code]
|
||||
.row
|
||||
.four.columns.alpha= label_tag :csv, "Download as CSV:"
|
||||
.twelve.columns.omega= check_box_tag :csv
|
||||
.row
|
||||
.four.columns.alpha= button t(:search)
|
||||
|
||||
|
||||
%table#listing_invoices.index
|
||||
%thead
|
||||
%tr
|
||||
- @report.header.each do |header|
|
||||
%th= header
|
||||
%tbody
|
||||
- @report.table.each do |row|
|
||||
%tr
|
||||
- row.each do |column|
|
||||
%td= column
|
||||
- if @report.table.empty?
|
||||
%tr
|
||||
%td{:colspan => "2"}= t(:none)
|
||||
@@ -136,6 +136,7 @@ Spree::Core::Engine.routes.prepend do
|
||||
match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management"
|
||||
match '/admin/reports/products_and_inventory' => 'admin/reports#products_and_inventory', :as => "products_and_inventory_admin_reports", :via => [:get, :post]
|
||||
match '/admin/reports/customers' => 'admin/reports#customers', :as => "customers_admin_reports", :via => [:get, :post]
|
||||
match '/admin/reports/xero_invoices' => 'admin/reports#xero_invoices', :as => "xero_invoices_admin_reports", :via => [:get, :post]
|
||||
match '/admin', :to => 'admin/overview#index', :as => :admin
|
||||
match '/admin/payment_methods/show_provider_preferences' => 'admin/payment_methods#show_provider_preferences', :via => :get
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
module OpenFoodNetwork
|
||||
class OrderAndDistributorReport
|
||||
|
||||
@@ -8,14 +7,15 @@ module OpenFoodNetwork
|
||||
|
||||
def header
|
||||
["Order date", "Order Id",
|
||||
"Customer Name","Customer Email", "Customer Phone", "Customer City",
|
||||
"SKU", "Item name", "Variant", "Quantity", "Max Quantity", "Cost", "Shipping cost",
|
||||
"Payment method",
|
||||
"Distributor", "Distributor address", "Distributor city", "Distributor postcode", "Shipping instructions"]
|
||||
"Customer Name","Customer Email", "Customer Phone", "Customer City",
|
||||
"SKU", "Item name", "Variant", "Quantity", "Max Quantity", "Cost", "Shipping cost",
|
||||
"Payment method",
|
||||
"Distributor", "Distributor address", "Distributor city", "Distributor postcode", "Shipping instructions"]
|
||||
end
|
||||
|
||||
def table
|
||||
order_and_distributor_details = []
|
||||
|
||||
@orders.each do |order|
|
||||
order.line_items.each do |line_item|
|
||||
order_and_distributor_details << [order.created_at, order.id,
|
||||
@@ -25,6 +25,7 @@ module OpenFoodNetwork
|
||||
order.distributor.andand.name, order.distributor.address.address1, order.distributor.address.city, order.distributor.address.zipcode, order.special_instructions ]
|
||||
end
|
||||
end
|
||||
|
||||
order_and_distributor_details
|
||||
end
|
||||
end
|
||||
|
||||
104
lib/open_food_network/xero_invoices_report.rb
Normal file
104
lib/open_food_network/xero_invoices_report.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
module OpenFoodNetwork
|
||||
class XeroInvoicesReport
|
||||
def initialize(orders, opts={})
|
||||
@orders = orders
|
||||
|
||||
@opts = opts.
|
||||
reject { |k, v| v.blank? }.
|
||||
reverse_merge({invoice_date: Date.today,
|
||||
due_date: 2.weeks.from_now.to_date,
|
||||
account_code: 'food sales'})
|
||||
end
|
||||
|
||||
def header
|
||||
%w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?)
|
||||
end
|
||||
|
||||
def table
|
||||
rows = []
|
||||
|
||||
@orders.each_with_index do |order, i|
|
||||
invoice_number = invoice_number_for(order, i)
|
||||
rows += rows_for_order(order, invoice_number, @opts)
|
||||
end
|
||||
|
||||
rows
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def invoice_number_for(order, i)
|
||||
@opts[:initial_invoice_number] ? @opts[:initial_invoice_number].to_i+i : order.number
|
||||
end
|
||||
|
||||
def rows_for_order(order, invoice_number, opts)
|
||||
[
|
||||
summary_row(order, 'Total untaxable produce (no tax)', total_untaxable_products(order), invoice_number, 'GST Free Income', opts),
|
||||
summary_row(order, 'Total taxable produce (tax inclusive)', total_taxable_products(order), invoice_number, 'GST on Income', opts),
|
||||
summary_row(order, 'Total untaxable fees (no tax)', total_untaxable_fees(order), invoice_number, 'GST Free Income', opts),
|
||||
summary_row(order, 'Total taxable fees (tax inclusive)', total_taxable_fees(order), invoice_number, 'GST on Income', opts),
|
||||
summary_row(order, 'Delivery Shipping Cost (tax inclusive)', total_shipping(order), invoice_number, tax_on_shipping_s(order), opts)
|
||||
].compact
|
||||
end
|
||||
|
||||
def summary_row(order, description, amount, invoice_number, tax_type, opts={})
|
||||
return nil if amount == 0
|
||||
|
||||
[order.bill_address.full_name,
|
||||
order.email,
|
||||
order.bill_address.address1,
|
||||
order.bill_address.address2,
|
||||
'',
|
||||
'',
|
||||
order.bill_address.city,
|
||||
order.bill_address.state,
|
||||
order.bill_address.zipcode,
|
||||
order.bill_address.country.andand.name,
|
||||
invoice_number,
|
||||
order.number,
|
||||
opts[:invoice_date],
|
||||
opts[:due_date],
|
||||
'',
|
||||
description,
|
||||
'1',
|
||||
amount,
|
||||
'',
|
||||
opts[:account_code],
|
||||
tax_type,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
Spree::Config.currency,
|
||||
'',
|
||||
order.paid? ? 'Y' : 'N'
|
||||
]
|
||||
end
|
||||
|
||||
def total_untaxable_products(order)
|
||||
order.line_items.without_tax.sum &:amount
|
||||
end
|
||||
|
||||
def total_taxable_products(order)
|
||||
order.line_items.with_tax.sum &:amount
|
||||
end
|
||||
|
||||
def total_untaxable_fees(order)
|
||||
order.adjustments.enterprise_fee.without_tax.sum &:amount
|
||||
end
|
||||
|
||||
def total_taxable_fees(order)
|
||||
order.adjustments.enterprise_fee.with_tax.sum &:amount
|
||||
end
|
||||
|
||||
def total_shipping(order)
|
||||
order.adjustments.shipping.sum &:amount
|
||||
end
|
||||
|
||||
def tax_on_shipping_s(order)
|
||||
tax_on_shipping = order.adjustments.shipping.sum(&:included_tax) > 0
|
||||
tax_on_shipping ? 'GST on Income' : 'GST Free Income'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -300,4 +300,94 @@ feature %q{
|
||||
].sort
|
||||
end
|
||||
end
|
||||
|
||||
describe "Xero invoices report" do
|
||||
let(:distributor1) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:distributor2) { create(:distributor_enterprise, with_payment_and_shipping: true, charges_sales_tax: true) }
|
||||
let(:user1) { create_enterprise_user enterprises: [distributor1] }
|
||||
let(:user2) { create_enterprise_user enterprises: [distributor2] }
|
||||
let(:shipping_method) { create(:shipping_method, name: "Shipping", description: "Expensive", calculator: Spree::Calculator::FlatRate.new(preferred_amount: 100.55)) }
|
||||
let(:enterprise_fee1) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 10)) }
|
||||
let(:enterprise_fee2) { create(:enterprise_fee, enterprise: user1.enterprises.first, tax_category: product2.tax_category, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 20)) }
|
||||
let(:order_cycle) { create(:simple_order_cycle, coordinator: distributor1, coordinator_fees: [enterprise_fee1, enterprise_fee2], distributors: [distributor1], variants: [product1.master]) }
|
||||
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
let(:country) { Spree::Country.find Spree::Config.default_country_id }
|
||||
let(:bill_address) { create(:address, firstname: 'Customer', lastname: 'Name', address1: 'customer l1', address2: '', city: 'customer city', zipcode: 1234, country: country) }
|
||||
let(:order1) { create(:order, order_cycle: order_cycle, distributor: user1.enterprises.first, shipping_method: shipping_method, bill_address: bill_address) }
|
||||
let(:product1) { create(:taxed_product, zone: zone, price: 12.54, tax_rate_amount: 0) }
|
||||
let(:product2) { create(:taxed_product, zone: zone, price: 500.15, tax_rate_amount: 0.2) }
|
||||
|
||||
let!(:line_item1) { create(:line_item, variant: product1.master, price: 12.54, quantity: 1, order: order1) }
|
||||
let!(:line_item2) { create(:line_item, variant: product2.master, price: 500.15, quantity: 3, order: order1) }
|
||||
|
||||
let!(:adj_shipping) { create(:adjustment, adjustable: order1, label: "Shipping", originator: shipping_method, amount: 100.55, included_tax: 10.06) }
|
||||
let!(:adj_fee1) { create(:adjustment, adjustable: order1, originator: enterprise_fee1, label: "Enterprise fee untaxed", amount: 10, included_tax: 0) }
|
||||
let!(:adj_fee2) { create(:adjustment, adjustable: order1, originator: enterprise_fee2, label: "Enterprise fee taxed", amount: 20, included_tax: 2) }
|
||||
|
||||
|
||||
before do
|
||||
order1.update_attribute :email, 'customer@email.com'
|
||||
Timecop.travel(Time.zone.local(2015, 4, 25, 14, 0, 0)) { order1.finalize! }
|
||||
|
||||
login_to_admin_section
|
||||
click_link 'Reports'
|
||||
|
||||
click_link 'Xero Invoices'
|
||||
end
|
||||
|
||||
around do |example|
|
||||
Timecop.travel(Time.zone.local(2015, 4, 26, 14, 0, 0)) do
|
||||
example.yield
|
||||
end
|
||||
end
|
||||
|
||||
it "shows Xero invoices report" do
|
||||
xero_invoice_table.should match_table [
|
||||
xero_invoice_header,
|
||||
xero_invoice_row('Total untaxable produce (no tax)', 12.54, 'GST Free Income'),
|
||||
xero_invoice_row('Total taxable produce (tax inclusive)', 1500.45, 'GST on Income'),
|
||||
xero_invoice_row('Total untaxable fees (no tax)', 10.0, 'GST Free Income'),
|
||||
xero_invoice_row('Total taxable fees (tax inclusive)', 20.0, 'GST on Income'),
|
||||
xero_invoice_row('Delivery Shipping Cost (tax inclusive)', 100.55, 'GST on Income')
|
||||
]
|
||||
end
|
||||
|
||||
it "can customise a number of fields" do
|
||||
fill_in 'initial_invoice_number', with: '5'
|
||||
fill_in 'invoice_date', with: '2015-02-12'
|
||||
fill_in 'due_date', with: '2015-03-12'
|
||||
fill_in 'account_code', with: 'abc123'
|
||||
click_button 'Search'
|
||||
|
||||
opts = {invoice_number: '5', invoice_date: '2015-02-12', due_date: '2015-03-12', account_code: 'abc123'}
|
||||
|
||||
xero_invoice_table.should match_table [
|
||||
xero_invoice_header,
|
||||
xero_invoice_row('Total untaxable produce (no tax)', 12.54, 'GST Free Income', opts),
|
||||
xero_invoice_row('Total taxable produce (tax inclusive)', 1500.45, 'GST on Income', opts),
|
||||
xero_invoice_row('Total untaxable fees (no tax)', 10.0, 'GST Free Income', opts),
|
||||
xero_invoice_row('Total taxable fees (tax inclusive)', 20.0, 'GST on Income', opts),
|
||||
xero_invoice_row('Delivery Shipping Cost (tax inclusive)', 100.55, 'GST on Income', opts)
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def xero_invoice_table
|
||||
find("table#listing_invoices")
|
||||
end
|
||||
|
||||
def xero_invoice_header
|
||||
%w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4 POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?)
|
||||
end
|
||||
|
||||
def xero_invoice_row(description, amount, tax_type, opts={})
|
||||
opts.reverse_merge!({invoice_number: order1.number, invoice_date: '2015-04-26', due_date: '2015-05-10', account_code: 'food sales'})
|
||||
|
||||
['Customer Name', 'customer@email.com', 'customer l1', '', '', '', 'customer city', 'Victoria', '1234', country.name, opts[:invoice_number], order1.number, opts[:invoice_date], opts[:due_date], '', description, '1', amount.to_s, '', opts[:account_code], tax_type, '', '', '', '', Spree::Config.currency, '', 'N']
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
37
spec/lib/open_food_network/xero_invoices_report_spec.rb
Normal file
37
spec/lib/open_food_network/xero_invoices_report_spec.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
require 'open_food_network/xero_invoices_report'
|
||||
|
||||
module OpenFoodNetwork
|
||||
describe XeroInvoicesReport do
|
||||
subject { XeroInvoicesReport.new [] }
|
||||
|
||||
describe "option defaults" do
|
||||
let(:report) { XeroInvoicesReport.new [], {initial_invoice_number: '', invoice_date: '', due_date: '', account_code: ''} }
|
||||
|
||||
around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } }
|
||||
|
||||
it "uses defaults when blank params are passed" do
|
||||
report.instance_variable_get(:@opts).should == {invoice_date: Date.civil(2015, 5, 5),
|
||||
due_date: Date.civil(2015, 5, 19),
|
||||
account_code: 'food sales'}
|
||||
end
|
||||
end
|
||||
|
||||
describe "generating invoice numbers" do
|
||||
let(:order) { double(:order, number: 'R731032860') }
|
||||
|
||||
describe "when no initial invoice number is given" do
|
||||
it "returns the order number" do
|
||||
subject.send(:invoice_number_for, order, 123).should == 'R731032860'
|
||||
end
|
||||
end
|
||||
|
||||
describe "when an initial invoice number is given" do
|
||||
subject { XeroInvoicesReport.new [], {initial_invoice_number: '123'} }
|
||||
|
||||
it "increments the number by the index" do
|
||||
subject.send(:invoice_number_for, order, 456).should == 579
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,21 @@ module Spree
|
||||
adjustment.metadata.should be
|
||||
end
|
||||
|
||||
describe "finding adjustments with and without tax included" do
|
||||
let!(:adjustment_with_tax) { create(:adjustment, included_tax: 123) }
|
||||
let!(:adjustment_without_tax) { create(:adjustment, included_tax: 0) }
|
||||
|
||||
it "finds adjustments with tax" do
|
||||
Adjustment.with_tax.should include adjustment_with_tax
|
||||
Adjustment.with_tax.should_not include adjustment_without_tax
|
||||
end
|
||||
|
||||
it "finds adjustments without tax" do
|
||||
Adjustment.without_tax.should include adjustment_without_tax
|
||||
Adjustment.without_tax.should_not include adjustment_with_tax
|
||||
end
|
||||
end
|
||||
|
||||
describe "recording included tax" do
|
||||
describe "TaxRate adjustments" do
|
||||
let!(:zone) { create(:zone_with_member) }
|
||||
|
||||
@@ -24,6 +24,22 @@ module Spree
|
||||
LineItem.supplied_by_any([s2]).should == [li2]
|
||||
LineItem.supplied_by_any([s1, s2]).should match_array [li1, li2]
|
||||
end
|
||||
|
||||
describe "finding line items with and without tax" do
|
||||
let(:tax_rate) { create(:tax_rate, calculator: Spree::Calculator::DefaultTax.new) }
|
||||
let!(:adjustment1) { create(:adjustment, adjustable: li1, originator: tax_rate, label: "TR", amount: 123, included_tax: 10.00) }
|
||||
let!(:adjustment2) { create(:adjustment, adjustable: li1, originator: tax_rate, label: "TR", amount: 123, included_tax: 10.00) }
|
||||
|
||||
before { li1; li2 }
|
||||
|
||||
it "finds line items with tax" do
|
||||
LineItem.with_tax.should == [li1]
|
||||
end
|
||||
|
||||
it "finds line items without tax" do
|
||||
LineItem.without_tax.should == [li2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "calculating price with adjustments" do
|
||||
|
||||
@@ -26,3 +26,44 @@ RSpec::Matchers.define :have_table_row do |row|
|
||||
node.all('tr').map { |tr| tr.all('th, td').map(&:text) }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
# find("#my-table").should match_table [[...]]
|
||||
RSpec::Matchers.define :match_table do |expected_table|
|
||||
|
||||
match_for_should do |node|
|
||||
rows = node.
|
||||
all("tr").
|
||||
map { |r| r.all("th,td").map { |c| c.text.strip } }
|
||||
|
||||
if rows.count != expected_table.count
|
||||
@failure_message = "found table with #{rows.count} rows, expected #{expected_table.count}"
|
||||
|
||||
else
|
||||
rows.each_with_index do |row, i|
|
||||
expected_row = expected_table[i]
|
||||
if row.count != expected_row.count
|
||||
@failure_message = "row #{i} has #{row.count} columns, expected #{expected_row.count}"
|
||||
break
|
||||
|
||||
elsif row != expected_row
|
||||
row.each_with_index do |cell, j|
|
||||
if cell != expected_row[j]
|
||||
@failure_message = "cell [#{i}, #{j}] has content '#{cell}', expected '#{expected_row[j]}'"
|
||||
break
|
||||
end
|
||||
end
|
||||
break if @failure_message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@failure_message.nil?
|
||||
end
|
||||
|
||||
failure_message_for_should do |text|
|
||||
@failure_message
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user