Files
openfoodnetwork/spec/system/admin/reports_spec.rb
Maikel Linke 23aa762be2 Add fallback report loading in case websockets fail
This also resolves a race condition scenario. Even if the report gets
rendered via websockets before the controller response is rendered then
the fallback script loads the report again. It's not the most beautiful
but probably okay until we replace websockts altogether.

I'm leaving websockets in at the moment because it can render the report
much quicker than polling can.
2024-08-16 15:24:34 +10:00

801 lines
30 KiB
Ruby

# frozen_string_literal: true
require "system_helper"
RSpec.describe '
As an administrator
I want numbers, all the numbers!
' do
include WebHelper
include AuthenticationHelper
context "Permissions for different reports" do
context "As an enterprise user" do
let(:user) do
create(:user, enterprises: [create(:distributor_enterprise)])
end
it "does not show super admin only report" do
login_as user
visit admin_reports_path
expect(page).not_to have_content "Users & Enterprises"
end
end
context "As an admin user" do
it "shows the super admin only report" do
login_to_admin_section
click_link "Reports"
expect(page).to have_content "Users & Enterprises"
end
end
end
describe "Background processing" do
it "renders UTF-8 characters" do
# We had a problem when UTF-8 was in the page and the report because
# ActiveStorage read ASCII.
# - https://github.com/openfoodfoundation/openfoodnetwork/issues/10758
#
# Create order to inject special characters:
order = create(:completed_order_with_totals)
# Render special characters in the page (filter option):
order.distributor.update!(name: "Späti")
# Render special character within the report:
order.billing_address.update!(lastname: "Müller")
# Run the report:
login_as_admin
visit admin_report_path(report_type: :customers)
run_report
expect(page).to have_content "Späti"
expect(page).to have_content "First Name Last Name Billing Address Email"
expect(page).to have_content "Müller"
end
it "requires confirmation to display big reports" do
# Mock data is much faster and accurate than creating many orders:
allow_any_instance_of(Reporting::Reports::Customers::Base)
.to receive(:columns).and_return(
{
first_name: proc { |_| "Little Bobby Tables " * (10**5) }, # 2 MB
}
)
# We still need an order for the report to render a row:
create(:completed_order_with_totals)
login_as_admin
visit admin_report_path(report_type: :customers)
run_report
expect(page).to have_content "This report is big"
expect(page).not_to have_content "Little Bobby Tables"
click_on "Display anyway"
expect(page).to have_content "First Name"
expect(page).to have_content "Little Bobby Tables"
end
it "displays a friendly timeout message and offers download" do
login_as_admin
visit admin_report_path(report_type: :customers)
stub_const("ReportJob::NOTIFICATION_TIME", 0)
run_report
# We also get an email.
perform_enqueued_jobs(only: ActionMailer::MailDeliveryJob)
email = ActionMailer::Base.deliveries.last
expect(email.body).to have_link(
"customers",
href: %r"^http://.*/rails/active_storage/disk/.*/customers_[0-9]+\.html$"
)
# ActiveStorage links usually expire after 5 minutes.
# But we want a longer expiry in emailed links.
parsed_email = Capybara::Node::Simple.new(email.body.to_s)
report_link = parsed_email.find(:link, "customers")[:href]
content = URI.parse(report_link).read
expect(content).to match "<th>\nFirst Name\n</th>"
# Let's also check the expiry of the emailed link:
Timecop.travel(3.days.from_now) do
content = URI.parse(report_link).read
expect(content).to match "<th>\nFirst Name\n</th>"
end
# The link should still expire though:
Timecop.travel(3.months.from_now) do
expect { URI.parse(report_link).read }
.to raise_error OpenURI::HTTPError, "404 Not Found"
end
end
it "allows the report to finish before the loading screen is rendered" do
login_as_admin
visit admin_report_path(report_type: :customers)
# The controller wants to execute the ReportJob in the background.
# But we change the logic here, execute it immediately and then wait
# until the report is displayed in the browser.
# The controller will still be waiting while the browser is receiving
# the report via web socket.
breakpoint = Mutex.new
breakpoint.lock
expect(ReportJob).to receive(:perform_later) do |**args|
ReportJob.perform_now(**args)
breakpoint.synchronize { "continue after unlocked" }
end
click_on "Go"
expect(page).to have_content "First Name Last Name Billing Address Email"
# Now that we see the report, we need to make sure that it's not replaced
# by the "loading" spinner when the controller action finishes.
# Unlocking the breakpoint will continue execution of the controller.
breakpoint.unlock
# We have to wait to be sure that the "loading" spinner won't appear
# within the next half second. The default wait time would wait for
# 10 seconds which slows down the spec.
using_wait_time 0.5 do
page.has_selector? ".loading"
end
expect(page).not_to have_selector ".loading"
end
end
describe "Can access Customers reports and generate customers report" do
before do
login_as_admin
visit admin_reports_path
end
it "customers report" do
within "table.index" do
click_link "Customers"
end
run_report
rows = find("table.report__table").all("thead tr")
table = rows.map { |r| r.all("th").map { |c| c.text.strip } }
expect(table.sort).to eq([
["First Name", "Last Name", "Billing Address", "Email", "Phone", "Hub", "Hub Address",
"Shipping Method", "Total Number of Orders", "Total incl. tax ($)",
"Last completed order date"]
].sort)
end
end
describe "Order cycle management report" do
before do
login_as_admin
visit admin_reports_path
end
it "payment method report" do
click_link "Payment Methods Report"
run_report
rows = find("table.report__table").all("thead tr")
table = rows.map { |r| r.all("th").map { |c| c.text.strip } }
expect(table.sort).to eq([
["First Name", "Last Name", "Hub", "Customer Code", "Email", "Phone", "Shipping Method",
"Payment Method", "Amount", "Balance"]
].sort)
end
it "delivery report" do
click_link "Delivery Report"
run_report
rows = find("table.report__table").all("thead tr")
table = rows.map { |r| r.all("th").map { |c| c.text.strip } }
expect(table.sort).to eq([
["First Name", "Last Name", "Hub", "Customer Code", "Delivery Address", "Delivery Postcode",
"Phone", "Shipping Method", "Payment Method", "Amount", "Balance",
"Temp Controlled Items?", "Special Instructions"]
].sort)
end
end
context "for a complete, paid order" do
let!(:ready_to_ship_order) { create(:order_ready_to_ship) }
before do
login_as_admin
visit admin_reports_path
end
it "generates the orders and distributors report" do
click_link 'Orders And Distributors'
run_report
rows = find("table.report__table").all("thead tr")
table_headers = rows.map { |r| r.all("th").map { |c| c.text.strip } }
expect(table_headers).to eq([
['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 Method',
'Shipping instructions']
])
expect(all('table.report__table tbody tr').count).to eq(
Spree::LineItem.where(
order_id: ready_to_ship_order.id # Total rows should equal number of line items, per order
).count
)
end
it "generates the payments reports" do
click_link 'Payments By Type'
run_report
rows = find("table.report__table").all("thead tr")
table_headers = rows.map { |r| r.all("th").map { |c| c.text.strip } }
expect(table_headers).to eq([
['Payment State',
'Distributor',
'Payment Type',
"Total (%s)" % currency_symbol]
])
expect(all('table.report__table tbody tr').count).to eq(
Spree::Payment.where(
order_id: ready_to_ship_order.id # Total rows should equal number of payments, per order
).count
)
end
end
context "sales tax 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(:user, enterprises: [distributor1]) }
let(:user2) { create(:user, enterprises: [distributor2]) }
let(:shipping_tax_rate) { create(:tax_rate, amount: 0.20, included_in_price: true, zone:) }
let(:shipping_tax_category) { create(:tax_category, tax_rates: [shipping_tax_rate]) }
let!(:shipping_method) {
create(:shipping_method_with, :expensive_name, distributors: [distributor1],
tax_category: shipping_tax_category)
}
let(:enterprise_fee) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: product2.variants.first.tax_category,
calculator: Calculator::FlatRate.new(preferred_amount: 120.0))
}
let(:order_cycle) {
create(:simple_order_cycle, coordinator: distributor1,
coordinator_fees: [enterprise_fee], distributors: [distributor1],
variants: [product1.variants.first, product2.variants.first])
}
let!(:zone) { create(:zone_with_member) }
let(:address) { create(:address) }
let(:order1) {
create(:order, order_cycle:, distributor: user1.enterprises.first,
ship_address: address, bill_address: address)
}
let(:product1) {
create(:taxed_product, zone:, price: 12.54, tax_rate_amount: 0, included_in_price: true)
}
let(:product2) {
create(:taxed_product, zone:, price: 500.15, tax_rate_amount: 0.2,
included_in_price: true)
}
let!(:line_item1) {
create(:line_item, variant: product1.variants.first, price: 12.54, quantity: 1, order: order1)
}
let!(:line_item2) {
create(:line_item, variant: product2.variants.first, price: 500.15, quantity: 3,
order: order1)
}
before do
order1.reload
break unless order1.next! until order1.delivery?
order1.select_shipping_method(shipping_method.id)
order1.recreate_all_fees!
break unless order1.next! until order1.payment?
create(:payment, state: "checkout", order: order1, amount: order1.reload.total,
payment_method: create(:payment_method, distributors: [distributor1]))
break unless order1.next! until order1.complete?
login_as_admin
visit admin_reports_path
end
it "generate Tax Types reports" do
click_link "Tax Types"
run_report
# Then it should give me access only to managed enterprises
expect(page).to have_select 'q_distributor_id_eq',
with_options: [user1.enterprises.first.name]
expect(page).not_to have_select 'q_distributor_id_eq',
with_options: [user2.enterprises.first.name]
# When I filter to just one distributor
select user1.enterprises.first.name, from: 'q_distributor_id_eq'
run_report
# Then I should see the relevant order
expect(page).to have_content order1.number.to_s
# And the totals and sales tax should be correct
expect(page).to have_content "1512.99" # items total
expect(page).to have_content "1500.45" # taxable items total
expect(page).to have_content "250.08" # sales tax
expect(page).to have_content "20.0" # enterprise fee tax
# And the shipping cost and tax should be correct
expect(page).to have_content "100.55" # shipping cost
expect(page).to have_content "16.76" # shipping tax
# And the total tax should be correct
expect(page).to have_content "286.84" # total tax
end
it "generate Tax Rates report" do
click_link "Tax Rates"
run_report
expect(page).to have_css(".report__table thead th", text: "20.0% ($)")
expect(page).to have_css(".report__table thead th", text: "0.0% ($)")
expect(page).to have_table_row [order1.number.to_s, "1446.7", "16.76", "0", "270.08",
"286.84", "1733.54"]
end
end
describe "products and inventory report" do
let(:supplier) { create(:supplier_enterprise, name: 'Supplier Name') }
let(:taxon) { create(:taxon, name: 'Taxon Name') }
let(:product1) {
create(:simple_product, name: "Product Name", price: 100, primary_taxon_id: taxon.id,
supplier_id: supplier.id)
}
let(:product2) {
create(:simple_product, name: "Product 2", price: 99.0, variant_unit: 'weight',
variant_unit_scale: 1, unit_value: '100',
primary_taxon_id: taxon.id, sku: "product_sku",
supplier_id: supplier.id)
}
let(:variant1) { product1.variants.first }
let(:variant2) {
create(:variant, product: product1, price: 80.0, primary_taxon: taxon, supplier:)
}
let(:variant3) { product2.variants.first }
before do
product1.set_property 'Organic', 'NASAA 12345'
product2.set_property 'Organic', 'NASAA 12345'
variant1.on_hand = 10
variant1.update!(sku: "sku1")
variant2.on_hand = 20
variant2.update!(sku: "sku2")
variant3.on_hand = 9
variant3.update!(sku: "")
end
it "shows products and inventory report" do
login_as_admin
visit admin_reports_path
expect(page).to have_content "All products"
expect(page).to have_content "Inventory (on hand)"
click_link 'All products'
run_report
expect(page).to have_content "Supplier"
expect(page).to have_table_row ["Supplier", "Producer Suburb", "Product",
"Product Properties", "Taxons", "Variant Value", "Price",
"Group Buy Unit Quantity", "Amount", "SKU",
"On Demand?", "On Hand"]
expect(page).to have_table_row [supplier.name, supplier.address.city,
"Product Name",
product1.properties.map(&:presentation).join(", "),
taxon.name, "1g", "100.0",
"none", "", "sku1", "No", "10"]
expect(page).to have_table_row [supplier.name, supplier.address.city,
"Product Name",
product1.properties.map(&:presentation).join(", "),
taxon.name, "1g", "80.0",
"none", "", "sku2", "No", "20"]
expect(page).to have_table_row [supplier.name, supplier.address.city,
"Product 2",
product1.properties.map(&:presentation).join(", "),
taxon.name, "100g", "99.0",
"none", "", "product_sku", "No", "9"]
end
it "shows the LettuceShare report" do
login_as_admin
visit admin_reports_path
click_link 'LettuceShare'
run_report
expect(page).to have_table_row ['PRODUCT', 'Description', 'Qty', 'Pack Size', 'Unit',
'Unit Price', 'Total', 'GST incl.',
'Grower and growing method', 'Taxon']
expect(page).to have_table_row ['Product 2', '100g', '', '100', 'g', '99.0', '', '0',
'Supplier Name (Organic - NASAA 12345)', 'Taxon Name']
end
end
describe "users and enterprises report" do
let!(:enterprise1) { create( :enterprise, owner: create(:user) ) }
let!(:enterprise2) { create( :enterprise, owner: create(:user) ) }
let!(:enterprise3) { create( :enterprise, owner: create(:user) ) }
before do
enterprise3.enterprise_roles.build( user: enterprise1.owner ).save
login_as_admin
visit admin_reports_path
click_link 'Users & Enterprises'
end
it "shows users and enterprises report" do
run_report
rows = find("table.report__table").all("tr")
table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[0..2] }
expect(table.sort).to eq([
["User", "Relationship", "Enterprise"],
[enterprise1.owner.email, "owns", enterprise1.name],
[enterprise1.owner.email, "manages", enterprise1.name],
[enterprise2.owner.email, "owns", enterprise2.name],
[enterprise2.owner.email, "manages", enterprise2.name],
[enterprise3.owner.email, "owns", enterprise3.name],
[enterprise3.owner.email, "manages", enterprise3.name],
[enterprise1.owner.email, "manages", enterprise3.name]
].sort)
end
it "filters the list" do
select enterprise3.name, from: "enterprise_id_in"
select enterprise1.owner.email, from: "user_id_in"
run_report
rows = find("table.report__table").all("tr")
table = rows.map { |r| r.all("th,td").map { |c| c.text.strip }[0..2] }
expect(table.sort).to eq([
["User", "Relationship", "Enterprise"],
[enterprise1.owner.email, "manages", enterprise3.name]
].sort)
end
end
describe 'bulk coop report' do
before do
login_as_admin
visit admin_reports_path
end
it "generating Bulk Co-op Supplier Report" do
click_link "Bulk Co-op Supplier Report"
run_report
expect(page).to have_table_row [
"Supplier",
"Product",
"Bulk Unit Size",
"Variant",
"Variant Value",
"Variant Unit",
"Weight",
"Sum Total",
"Units Required",
"Unallocated",
"Max Quantity Excess"
]
end
it "generating Bulk Co-op Allocation report" do
click_link "Bulk Co-op Allocation"
run_report
expect(page).to have_table_row [
"Customer",
"Product",
"Bulk Unit Size",
"Variant",
"Variant Value",
"Variant Unit",
"Weight",
"Sum Total",
"Total available",
"Unallocated",
"Max Quantity Excess"
]
end
it "generating Bulk Co-op Packing Sheets report" do
click_link "Bulk Co-op Packing Sheets"
run_report
expect(page).to have_table_row [
"Customer",
"Product",
"Variant",
"Sum Total"
]
end
it "generating Bulk Co-op Customer Payments report" do
click_link "Bulk Co-op Customer Payments"
run_report
expect(page).to have_table_row [
"Customer",
"Date of Order",
"Total Cost",
"Amount Owing",
"Amount Paid"
]
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(:user, enterprises: [distributor1]) }
let(:user2) { create(:user, enterprises: [distributor2]) }
let(:shipping_method) { create(:shipping_method_with, :expensive_name) }
let(:shipment) { create(:shipment_with, :shipping_method, shipping_method:) }
let(:enterprise_fee1) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: product2.variants.first.tax_category,
calculator: Calculator::FlatRate.new(preferred_amount: 10))
}
let(:enterprise_fee2) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: product2.variants.first.tax_category,
calculator: 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.variants.first])
}
let!(:zone) { create(:zone_with_member) }
let(:bill_address) {
create(:address, firstname: 'Customer', lastname: 'Name', address1: 'customer l1',
address2: '', city: 'customer city', zipcode: 1234)
}
let(:order1) {
create(:order, order_cycle:, distributor: user1.enterprises.first,
shipments: [shipment], bill_address:, state: 'payment')
}
let(:product1) {
create(:taxed_product, zone:, price: 12.54, tax_rate_amount: 0, sku: 'sku1')
}
let(:product2) {
create(:taxed_product, zone:, price: 500.15, tax_rate_amount: 0.2, sku: 'sku2')
}
describe "with adjustments" do
let!(:line_item1) {
create(:line_item, variant: product1.variants.first, price: 12.54, quantity: 1,
order: order1)
}
let!(:line_item2) {
create(:line_item, variant: product2.variants.first, price: 500.15, quantity: 3,
order: order1)
}
let!(:tax_category) { create(:tax_category) }
let!(:tax_rate) { create(:tax_rate, tax_category:) }
let!(:adj_shipping) {
create(:adjustment, order: order1, adjustable: order1, label: "Shipping",
originator: shipping_method, amount: 100.55)
}
let!(:adj_fee1) {
create(:adjustment, order: order1, adjustable: order1, originator: enterprise_fee1,
label: "Enterprise fee untaxed", amount: 10)
}
let!(:adj_fee2) {
create(:adjustment, order: order1, adjustable: order1, originator: enterprise_fee2,
label: "Enterprise fee taxed", amount: 20, tax_category:)
}
let!(:adj_fee2_tax) {
create(:adjustment, order: order1, adjustable: adj_fee2, originator: tax_rate, amount: 3,
state: "closed")
}
let!(:adj_admin1) {
create(:adjustment, order: order1, adjustable: order1, originator: nil,
label: "Manual adjustment", amount: 30)
}
let!(:adj_admin2) {
create(:adjustment, order: order1, adjustable: order1, originator: nil,
label: "Manual adjustment", amount: 40, tax_category:)
}
before do
order1.update_order!
order1.update!(email: 'customer@email.com')
order1.shipment.update(included_tax_total: 10.06)
Timecop.travel(Time.zone.local(2021, 4, 25, 14, 0, 0)) { order1.finalize! }
order1.reload
order1.create_tax_charge!
end
around do |example|
Timecop.travel(Time.zone.local(2021, 4, 26, 14, 0, 0)) do
example.run
end
end
context "summary report" do
before do
login_as_admin
visit admin_reports_path
click_link "Summary"
run_report
end
it "shows Xero invoices report" do
expect(xero_invoice_table).to match_table [
xero_invoice_header,
xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54,
'GST Free Income'),
xero_invoice_summary_row('Total taxable produce (tax inclusive)', 1500.45,
'GST on Income'),
xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0,
'GST Free Income'),
xero_invoice_summary_row('Total taxable fees (tax inclusive)', 20.0,
'GST on Income'),
xero_invoice_summary_row('Delivery Shipping Cost (tax inclusive)', 100.55,
'GST on Income'),
xero_invoice_summary_row('Total untaxable admin adjustments (no tax)', 30.0,
'GST Free Income'),
xero_invoice_summary_row('Total taxable admin adjustments (tax inclusive)', 40.0,
'GST on Income')
]
end
it "can customise a number of fields" do
fill_in 'initial_invoice_number', with: '5'
pick_datetime '#invoice_date', Date.new(2021, 2, 12)
pick_datetime '#due_date', Date.new(2021, 3, 12)
fill_in 'account_code', with: 'abc123'
run_report
opts = { invoice_number: '5', invoice_date: '2021-02-12',
due_date: '2021-03-12', account_code: 'abc123' }
expect(xero_invoice_table).to match_table [
xero_invoice_header,
xero_invoice_summary_row('Total untaxable produce (no tax)', 12.54,
'GST Free Income', opts),
xero_invoice_summary_row('Total taxable produce (tax inclusive)', 1500.45,
'GST on Income', opts),
xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0,
'GST Free Income', opts),
xero_invoice_summary_row('Total taxable fees (tax inclusive)', 20.0,
'GST on Income', opts),
xero_invoice_summary_row('Delivery Shipping Cost (tax inclusive)', 100.55,
'GST on Income', opts),
xero_invoice_summary_row('Total untaxable admin adjustments (no tax)', 30.0,
'GST Free Income', opts),
xero_invoice_summary_row('Total taxable admin adjustments (tax inclusive)', 40.0,
'GST on Income', opts)
]
end
end
context "detailed report" do
it "generates a detailed report" do
login_as_admin
visit admin_reports_path
click_link "Detailed"
run_report
opts = {}
expect(xero_invoice_table).to match_table [
xero_invoice_header,
xero_invoice_li_row(line_item1),
xero_invoice_li_row(line_item2),
xero_invoice_adjustment_row(adj_admin1),
xero_invoice_adjustment_row(adj_admin2),
xero_invoice_summary_row('Total untaxable fees (no tax)', 10.0,
'GST Free Income', opts),
xero_invoice_summary_row('Total taxable fees (tax inclusive)', 20.0,
'GST on Income', opts),
xero_invoice_summary_row('Delivery Shipping Cost (tax inclusive)', 100.55,
'GST on Income', opts)
]
end
end
end
private
def xero_invoice_table
find("table.report__table")
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_summary_row(description, amount, tax_type, opts = {})
xero_invoice_row '', description, amount, '1', tax_type, opts
end
def xero_invoice_li_row(line_item, opts = {})
tax_type = line_item.has_tax? ? 'GST on Income' : 'GST Free Income'
xero_invoice_row line_item.variant.sku, line_item.product_and_full_name,
line_item.price.to_s, line_item.quantity.to_s, tax_type, opts
end
def xero_invoice_adjustment_row(adjustment, opts = {})
tax_type = adjustment.has_tax? ? 'GST on Income' : 'GST Free Income'
xero_invoice_row('', adjustment.label, adjustment.amount, '1', tax_type, opts)
end
def xero_invoice_row(sku, description, amount, quantity, tax_type, opts = {})
opts.reverse_merge!(customer_name: 'Customer Name', address1: 'customer l1',
city: 'customer city', state: 'Victoria', zipcode: '1234',
country: 'Australia', invoice_number: order1.number,
order_number: order1.number, invoice_date: '2021-04-26',
due_date: '2021-05-26', account_code: 'food sales')
[opts[:customer_name], 'customer@email.com', opts[:address1], '', '', '',
opts[:city], opts[:state], opts[:zipcode], opts[:country], opts[:invoice_number],
opts[:order_number], opts[:invoice_date], opts[:due_date],
sku,
description,
quantity,
amount.to_s, '', opts[:account_code], tax_type, '', '', '', '', Spree::Config.currency,
'', 'N']
end
end
end