Files
openfoodnetwork/spec/system/admin/invoice_print_spec.rb
Gaetan Craig-Riou bc23423521 Remove the link between enterprise and internal payment method
Enterprise have access to the internal payment method by default.
The access is handled at the application level so we don't have to
manage database links.
2026-03-10 16:07:43 +11:00

739 lines
30 KiB
Ruby

# frozen_string_literal: true
require "system_helper"
RSpec.describe '
As an administrator
I want to print a invoice as PDF
', type: :feature do
include WebHelper
include AuthenticationHelper
let(:user) { create(:user) }
let(:product) { create(:simple_product) }
let(:distributor) {
create(:distributor_enterprise, owner: user, with_payment_and_shipping: true,
charges_sales_tax: true)
}
let(:order_cycle) do
create(:simple_order_cycle, name: 'One', distributors: [distributor],
variants: [product.variants.first])
end
let(:order) do
create(:order_with_totals_and_distribution, user:, distributor:,
completed_at: 1.day.ago,
order_cycle:, state: 'complete',
payment_state: 'balance_due')
end
before do
Capybara.current_driver = :rack_test
# return a duplicate empaty string for CSS pack request like:
# 'http://test.host/packs-test/css/mail-1ab2dc7f.css'
# This is because Wicked PDF will try to force an encoding on the returned string, which will
# break with a frozen string
stub_request(:get, ->(uri) { uri.to_s.include? "/css/mail" }).to_return(body: "".dup)
end
after do
Capybara.use_default_driver
end
shared_examples "contains right Payment Description at Checkout information" do
let(:url_params) { {} }
let!(:payment_method1) do
create(:stripe_sca_payment_method, distributors: [distributor], description: "description1")
end
let!(:payment_method2) do
create(:stripe_sca_payment_method, distributors: [distributor], description: "description2")
end
context "with no payment" do
it "do not display the payment description information" do
login_as_admin
visit spree.print_admin_order_path(order, params: url_params)
convert_pdf_to_page
expect(page).not_to have_content 'Payment Description at Checkout'
end
end
context "with one payment" do
let!(:payment1) do
create(:payment, :completed, order:, payment_method: payment_method1)
end
before do
order.save!
end
it "display the payment description section" do
login_as_admin
visit spree.print_admin_order_path(order, params: url_params)
convert_pdf_to_page
expect(page).to have_content 'Payment Description at Checkout'
expect(page).to have_content 'description1'
end
end
context "with two payments, and one that failed" do
before do
order.update payments: []
order.payments << create(:payment, :completed, order:,
payment_method: payment_method1,
created_at: 1.day.ago)
order.payments << create(:payment, order:, state: 'failed',
payment_method: payment_method2,
created_at: 2.days.ago)
order.save!
end
it "display the payment description section and use the one from the completed payment" do
login_as_admin
visit spree.print_admin_order_path(order, params: url_params)
convert_pdf_to_page
expect(page).to have_content 'Payment Description at Checkout'
expect(page).to have_content 'description1'
end
end
context "with two completed payments" do
before do
order.update payments: []
order.payments << create(:payment, :completed, order:,
payment_method: payment_method1,
created_at: 2.days.ago)
# Use an internal payment method so we also test the name translation
order.payments << create(
:payment,
:completed,
order:,
payment_method: create(:customer_credit_payment_method),
created_at: 1.day.ago
)
order.save!
end
it "display the payment description section and use the one from the last payment" do
login_as_admin
visit spree.print_admin_order_path(order, params: url_params)
convert_pdf_to_page
expect(page).to have_content 'Payment Description at Checkout'
expect(page).to have_content 'Customer credit'
end
end
end
shared_examples "Check display on each invoice: legacy and alternative" do |alternative_invoice|
let!(:completed_order) do
create(:completed_order_with_fees, distributor:, order_cycle:,
user: create(:user, email: "xxxxxx@example.com"),
bill_address: create(:address, phone: '1234567890'))
end
let(:url_params) { {} }
before do
allow(Spree::Config).to receive(:invoice_style2?).and_return(alternative_invoice)
login_as_admin
visit spree.print_admin_order_path(completed_order, params: url_params)
convert_pdf_to_page
end
it "display phone number and email of the customer" do
expect(page).to have_content "1234567890"
expect(page).to have_content "xxxxxx@example.com"
end
end
context "when invoice feature is not enabled" do
it_behaves_like "contains right Payment Description at Checkout information"
it_behaves_like "Check display on each invoice: legacy and alternative", false
it_behaves_like "Check display on each invoice: legacy and alternative", true
describe "order with taxes" do
let(:user1) { create(:user, enterprises: [distributor]) }
let!(:zone) { create(:zone_with_member) }
let(:address) { create(:address) }
context "included" do
let(:shipping_tax_rate_included) {
create(:tax_rate, amount: 0.1, included_in_price: true, zone:)
}
let(:enterprise_fee_rate_included) {
create(:tax_rate, amount: 0.15, included_in_price: true, zone:)
}
let(:shipping_tax_category) {
create(:tax_category, tax_rates: [shipping_tax_rate_included])
}
let(:fee_tax_category) {
create(:tax_category, tax_rates: [enterprise_fee_rate_included])
}
let!(:shipping_method) {
create(:shipping_method_with, :expensive_name, distributors: [distributor],
tax_category: shipping_tax_category)
}
let!(:enterprise_fee) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: fee_tax_category,
calculator: Calculator::FlatRate.new(preferred_amount: 120.0))
}
let!(:order_cycle) {
create(:simple_order_cycle,
coordinator: distributor,
coordinator_fees: [enterprise_fee],
distributors: [distributor],
variants: [product1.variants.first, product2.variants.first])
}
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)
}
let(:url_params) {
if OpenFoodNetwork::FeatureToggle.enabled?(:invoices)
{ invoice_id: order1.invoices.first.id }
else
{}
end
}
before do
order1.reload
while !order1.delivery?
break if !order1.next!
end
order1.select_shipping_method(shipping_method.id)
order1.recreate_all_fees!
while !order1.payment?
break if !order1.next!
end
create(:payment, state: "checkout", order: order1, amount: order1.reload.total,
payment_method: create(:payment_method, distributors: [distributor]))
while !order1.complete?
break if !order1.next!
end
order1.invoices.create!
end
context "legacy invoice" do
before do
allow(Spree::Config).to receive(:invoice_style2?).and_return(false)
login_as_admin
visit spree.print_admin_order_path(order1, params: url_params)
convert_pdf_to_page
end
it "displays $0.00 when a line item has no tax" do
expect(page).to have_content Spree::Product.first.name
expect(page).to have_content "(1g)"
expect(page).to have_content "1 $0.00 $12.54"
end
it "displays the taxes correctly" do
# header
expect(page).to have_content "Item Qty GST Price"
# second line item, included tax
expect(page).to have_content Spree::Product.second.name.to_s
expect(page).to have_content "(1g)" # display as
expect(page).to have_content "3 $250.08 $1,500.45"
# Enterprise fee
expect(page).to have_content "Whole order - #{
enterprise_fee.name
} fee by coordinator " \
"#{
user1.enterprises.first.name
} 1 $15.65 (included) $120.00"
# Shipping
expect(page).to have_content "Shipping 1 $9.14 (included) $100.55"
# Order Totals
expect(page).to have_content "GST Total: $274.87"
expect(page).to have_content "Total (Excl. tax): $1,458.67"
expect(page).to have_content "Total (Incl. tax): $1,733.54"
end
end
context "alternative invoice" do
before do
allow(Spree::Config).to receive(:invoice_style2?).and_return(true)
login_as_admin
visit spree.print_admin_order_path(order1, params: url_params)
convert_pdf_to_page
end
it "displays the taxes correctly" do
# header
expect(page).to have_content "Item Qty"
expect(page).to have_content "Unit price (Incl. tax)"
expect(page).to have_content "Total price (Incl. tax)"
expect(page).to have_content "Tax rate"
# first line item, no tax
expect(page).to have_content Spree::Product.first.name.to_s
expect(page).to have_content "(1g)" # display as
expect(page).to have_content "1 $12.54 $12.54 0.0%"
# second line item, included tax
expect(page).to have_content Spree::Product.second.name.to_s
expect(page).to have_content "(1g)" # display as
expect(page).to have_content "3 $500.15 $1,500.45 20.0%"
# Enterprise fee
expect(page).to have_content "#{enterprise_fee.name} fee by coordinator " \
"#{user1.enterprises.first.name} $120.00"
# Shipping
expect(page).to have_content "Shipping $100.55 10.0%"
# Tax totals
expect(page).to have_content "Total tax (10.0%): $9.14 " \
"Total tax (15.0%): $15.65 Total tax (20.0%): $250.08"
# Order Totals
expect(page).to have_content "Total (Incl. tax): $1,733.54"
expect(page).to have_content "Total (Excl. tax): $1,458.67"
end
end
context "Line item with variant having variant_unit as 'items'" do
before do
line_item1.variant.update!(variant_unit: "items", display_as: "1 bucket",
variant_unit_name: "bucket")
login_as_admin
visit spree.print_admin_order_path(order1, params: url_params)
convert_pdf_to_page
end
it 'should have correct display as value' do
# first line item
expect(page).to have_content Spree::Product.first.name.to_s
expect(page).to have_content "(1 bucket)" # display as
end
end
end
context "added" do
let(:shipping_tax_rate_added) {
create(:tax_rate, amount: 0.10, included_in_price: false, zone:)
}
let(:enterprise_fee_rate_added) {
create(:tax_rate, amount: 0.15, included_in_price: false, zone:)
}
let(:shipping_tax_category) {
create(:tax_category, tax_rates: [shipping_tax_rate_added])
}
let(:fee_tax_category) { create(:tax_category, tax_rates: [enterprise_fee_rate_added]) }
let!(:shipping_method) {
create(:shipping_method_with, :expensive_name, distributors: [distributor],
tax_category: shipping_tax_category)
}
let(:enterprise_fee) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: fee_tax_category,
calculator: Calculator::FlatRate.new(preferred_amount: 120.0))
}
let(:order_cycle2) {
create(:simple_order_cycle, coordinator: distributor,
coordinator_fees: [enterprise_fee],
distributors: [distributor],
variants: [product3.variants.first, product4.variants.first])
}
let(:order2) {
create(:order, order_cycle: order_cycle2, distributor: user1.enterprises.first,
ship_address: address, bill_address: address)
}
let(:product3) {
create(:taxed_product, zone:, price: 12.54, tax_rate_amount: 0,
included_in_price: false)
}
let(:product4) {
create(:taxed_product, zone:, price: 500.15, tax_rate_amount: 0.2,
included_in_price: false)
}
let!(:line_item3) {
create(:line_item, variant: product3.variants.first, price: 12.54, quantity: 1,
order: order2)
}
let!(:line_item4) {
create(:line_item, variant: product4.variants.first, price: 500.15, quantity: 3,
order: order2)
}
before do
order2.reload
while !order2.delivery?
break if !order2.next!
end
order2.select_shipping_method(shipping_method.id)
order2.recreate_all_fees!
while !order2.payment?
break if !order2.next!
end
create(:payment, state: "checkout", order: order2, amount: order2.reload.total,
payment_method: create(:payment_method, distributors: [distributor]))
while !order2.complete?
break if !order2.next!
end
end
context "legacy invoice" do
before do
allow(Spree::Config).to receive(:invoice_style2?).and_return(false)
login_as_admin
visit spree.print_admin_order_path(order2)
convert_pdf_to_page
end
it "displays $0.0 when a line item has no tax" do
expect(page).to have_content Spree::Product.first.name
expect(page).to have_content "(1g)"
expect(page).to have_content "1 $0.00 $12.54"
end
it "displays the added tax on the GST colum" do
expect(page).to have_content Spree::Product.second.name.to_s
expect(page).to have_content "(1g)" # display as
expect(page).to have_content "3 $300.09 $1,500.45"
end
it "displays the taxes correctly" do
# header
expect(page).to have_content "Item Qty GST Price"
# Enterprise fee
expect(page).to have_content "Whole order - #{
enterprise_fee.name
} fee by coordinator " \
"#{
user1.enterprises.first.name
} 1 $18.00 $120.00"
# Shipping
expect(page).to have_content "Shipping 1 $10.06 $100.55"
# Order Totals
expect(page).to have_content "GST Total: $328.15"
expect(page).to have_content "Total (Excl. tax): $1,733.54"
expect(page).to have_content "Total (Incl. tax): $2,061.69"
end
end
context "alternative invoice" do
before do
allow(Spree::Config).to receive(:invoice_style2?).and_return(true)
login_as_admin
visit spree.print_admin_order_path(order2)
convert_pdf_to_page
end
it "displays the taxes correctly" do
# header
expect(page).to have_content "Item Qty"
expect(page).to have_content "Unit price (Incl. tax)"
expect(page).to have_content "Total price (Incl. tax)"
expect(page).to have_content "Tax rate"
# first line item, no tax
expect(page).to have_content Spree::Product.first.name.to_s
expect(page).to have_content "(1g)" # display as
expect(page).to have_content "1 $12.54 $12.54 0.0%"
# second line item, included tax
expect(page).to have_content Spree::Product.second.name.to_s
expect(page).to have_content "(1g)" # display as
expect(page).to have_content "3 $500.15 $1,500.45 20.0%"
# Enterprise fee
expect(page).to have_content "#{enterprise_fee.name} fee by coordinator " \
"#{user1.enterprises.first.name} $120.00"
# Shipping
expect(page).to have_content "Shipping $100.55 10.0%"
# Tax totals
expect(page).to have_content "Total tax (10.0%): $10.06 " \
"Total tax (15.0%): $18.00 Total tax (20.0%): $300.09"
# Order Totals
expect(page).to have_content "Total (Incl. tax): $2,061.69"
expect(page).to have_content "Total (Excl. tax): $1,733.54"
end
end
end
end
end
context "when invoice feature is enabled", feature: :invoices do
it_behaves_like "contains right Payment Description at Checkout information"
it_behaves_like "Check display on each invoice: legacy and alternative", false
it_behaves_like "Check display on each invoice: legacy and alternative", true
describe "order with taxes" do
let(:user1) { create(:user, enterprises: [distributor]) }
let!(:zone) { create(:zone_with_member) }
let(:address) { create(:address) }
context "included" do
let(:shipping_tax_rate_included) {
create(:tax_rate, amount: 0.1, included_in_price: true, zone:)
}
let(:enterprise_fee_rate_included) {
create(:tax_rate, amount: 0.15, included_in_price: true, zone:)
}
let(:shipping_tax_category) {
create(:tax_category, tax_rates: [shipping_tax_rate_included])
}
let(:fee_tax_category) {
create(:tax_category, tax_rates: [enterprise_fee_rate_included])
}
let!(:shipping_method_name) { "SM1" }
let!(:shipping_method) {
create(:shipping_method_with, :expensive_name, name: shipping_method_name,
distributors: [distributor],
tax_category: shipping_tax_category)
}
let!(:enterprise_fee) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: fee_tax_category,
calculator: Calculator::FlatRate.new(preferred_amount: 120.0))
}
let!(:order_cycle) {
create(:simple_order_cycle,
coordinator: distributor,
coordinator_fees: [enterprise_fee],
distributors: [distributor],
variants: [product1.variants.first, product2.variants.first])
}
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)
}
let(:url_params) { {} }
before do
order1.reload
while !order1.delivery?
break if !order1.next!
end
order1.select_shipping_method(shipping_method.id)
order1.recreate_all_fees!
while !order1.payment?
break if !order1.next!
end
create(:payment, state: "checkout", order: order1, amount: order1.reload.total,
payment_method: create(:payment_method, distributors: [distributor]))
while !order1.complete?
break if !order1.next!
end
order1.invoices.create!
login_as_admin
visit spree.print_admin_order_path(order1, params: url_params)
convert_pdf_to_page
end
it "displays the taxes correctly" do
# header
expect(page).to have_content "Item Qty"
expect(page).to have_content "Weight / VOL."
expect(page).to have_content "Price Per unit (Excl."
expect(page).to have_content "Total price (Excl."
expect(page).to have_content "Tax rate"
expect(page).to have_content "Total price (Incl."
# first line item, no tax
expect(page).to have_content Spree::Product.first.name.to_s
expect(page).to have_content "($12,540.00 / kg)" # unit price
expect(page).to have_content "1 1g $12.54 $12.54 $12.54"
# # second line item, included tax
expect(page).to have_content Spree::Product.second.name.to_s
expect(page).to have_content "($500,150.00 / kg)" # unit price
expect(page).to have_content "3 1g $416.79 $1,250.37 20.0% $1,500.45"
# Enterprise fee
expect(page).to have_content(
"#{enterprise_fee.name} fee by coordinator $104.35 15.0% $120.00 #{distributor.name}"
)
# Shipping
expect(page).to have_content "Delivery (#{shipping_method_name}) $91.41 10.0% $100.55"
# Tax totals
expect(page).to have_content "Total tax (10.0%): $9.14 " \
"Total tax (15.0%): $15.65 Total tax (20.0%): $250.08"
expect(page).to have_content "Total tax: $274.87"
# Order Totals
expect(page).to have_content "Total (Incl. tax): $1,733.54"
expect(page).to have_content "Total (Excl. tax): $1,458.67"
end
end
context "added" do
let(:shipping_tax_rate_added) {
create(:tax_rate, amount: 0.10, included_in_price: false, zone:)
}
let(:enterprise_fee_rate_added) {
create(:tax_rate, amount: 0.15, included_in_price: false, zone:)
}
let(:shipping_tax_category) {
create(:tax_category, tax_rates: [shipping_tax_rate_added])
}
let(:fee_tax_category) { create(:tax_category, tax_rates: [enterprise_fee_rate_added]) }
let!(:shipping_method_name) { "SM2" }
let!(:shipping_method) {
create(:shipping_method_with, :expensive_name, name: shipping_method_name,
distributors: [distributor],
tax_category: shipping_tax_category)
}
let(:enterprise_fee) {
create(:enterprise_fee, enterprise: user1.enterprises.first,
tax_category: fee_tax_category,
calculator: Calculator::FlatRate.new(preferred_amount: 120.0))
}
let(:order_cycle2) {
create(:simple_order_cycle, coordinator: distributor,
coordinator_fees: [enterprise_fee],
distributors: [distributor],
variants: [product3.variants.first, product4.variants.first])
}
let(:order2) {
create(:order, order_cycle: order_cycle2, distributor: user1.enterprises.first,
ship_address: address, bill_address: address)
}
let(:product3) {
create(:taxed_product, zone:, price: 12.54, tax_rate_amount: 0,
included_in_price: false)
}
let(:product4) {
create(:taxed_product, zone:, price: 500.15, tax_rate_amount: 0.2,
included_in_price: false)
}
let!(:line_item3) {
create(:line_item, variant: product3.variants.first, price: 12.54, quantity: 1,
order: order2)
}
let!(:line_item4) {
create(:line_item, variant: product4.variants.first, price: 500.15, quantity: 3,
order: order2)
}
before do
order2.reload
while !order2.delivery?
break if !order2.next!
end
order2.select_shipping_method(shipping_method.id)
order2.recreate_all_fees!
while !order2.payment?
break if !order2.next!
end
create(:payment, state: "checkout", order: order2, amount: order2.reload.total,
payment_method: create(:payment_method, distributors: [distributor]))
while !order2.complete?
break if !order2.next!
end
login_as_admin
visit spree.print_admin_order_path(order2)
convert_pdf_to_page
end
it "displays the taxes correctly" do
# header
expect(page).to have_content "Item Qty"
expect(page).to have_content "Weight / VOL."
expect(page).to have_content "Price Per unit (Excl."
expect(page).to have_content "Total price (Excl."
expect(page).to have_content "Tax rate"
expect(page).to have_content "Total price (Incl."
# first line item, no tax
expect(page).to have_content Spree::Product.first.name.to_s
expect(page).to have_content "($12,540.00 / kg)" # unit price
expect(page).to have_content "1 1g $12.54 $12.54 $12.54"
# second line item, included tax
expect(page).to have_content Spree::Product.second.name.to_s
expect(page).to have_content "($500,150.00 / kg)" # unit price
expect(page).to have_content "3 1g $500.15 $1,500.45 20.0% $1,800.54"
# Enterprise fee
expect(page).to have_content(
"#{enterprise_fee.name} fee by coordinator $120.00 15.0% $138.00 #{distributor.name}"
)
# Shipping
expect(page).to have_content "Delivery (#{shipping_method_name}) $100.55 10.0% $110.61"
# Tax totals
expect(page).to have_content "Total tax (10.0%): $10.06 " \
"Total tax (15.0%): $18.00 Total tax (20.0%): $300.09"
expect(page).to have_content "Total tax: $328.15"
# Order Totals
expect(page).to have_content "Total (Incl. tax): $2,061.69"
expect(page).to have_content "Total (Excl. tax): $1,733.54"
end
end
end
describe "Rendering previous invoice number" do
context "Order doesn't have previous invoices" do
it "should display the invoice number" do
login_as_admin
visit spree.print_admin_order_path(order, params: {})
convert_pdf_to_page
expect(page).to have_content "#{order.distributor_id}-#{order.invoices.first.number}"
end
end
context "Order has previous invoices" do
before do
Orders::GenerateInvoiceService.new(order).generate_or_update_latest_invoice
first_line_item = order.line_items.first
order.line_items.first.update(quantity: first_line_item.quantity + 1)
end
it "should display the invoice number along with the latest invoice number" do
login_as_admin
visit spree.print_admin_order_path(order, params: {})
expect(order.invoices.count).to eq(2)
new_invoice_number = "#{order.distributor_id}-#{order.invoices.first.number}"
canceled_invoice_number = "#{order.distributor_id}-#{order.invoices.last.number}"
convert_pdf_to_page
expect(page).to have_content "#{new_invoice_number} cancels and replaces invoice #{
canceled_invoice_number}"
end
end
end
end
end
def convert_pdf_to_page
temp_pdf = Tempfile.new('pdf')
temp_pdf << page.source.force_encoding('UTF-8')
reader = PDF::Reader.new(temp_pdf)
# Call 'page.runs.map(&:text)' instead of 'page.text' because the latter doesn't return all text,
# see https://github.com/yob/pdf-reader/issues/518
pdf_text = reader.pages.map { |page| page.runs.map(&:text).join(' ') }
temp_pdf.close
page.driver.response.instance_variable_set('@body', pdf_text)
end