diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b553c7b74..66f7d5a3cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,7 +65,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests env: @@ -83,7 +83,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}" run: | git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3) - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec models: runs-on: ubuntu-22.04 @@ -106,10 +106,10 @@ jobs: # [n] - where the n is a number of parallel jobs you want to run your tests on. # Use a higher number if you have slow tests to split them between more parallel jobs. # Remember to update the value of the `ci_node_index` below to (0..n-1). - ci_node_total: [5] + ci_node_total: [4] # Indexes for parallel jobs (starting from zero). # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. - ci_node_index: [0, 1, 2, 3, 4] + ci_node_index: [0, 1, 2, 3] steps: - uses: actions/checkout@v3 @@ -125,7 +125,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests env: @@ -142,7 +142,7 @@ jobs: #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec system_admin: runs-on: ubuntu-22.04 @@ -165,10 +165,10 @@ jobs: # [n] - where the n is a number of parallel jobs you want to run your tests on. # Use a higher number if you have slow tests to split them between more parallel jobs. # Remember to update the value of the `ci_node_index` below to (0..n-1). - ci_node_total: [13] + ci_node_total: [14] # Indexes for parallel jobs (starting from zero). # E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc. - ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] steps: - uses: actions/checkout@v3 @@ -191,7 +191,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests @@ -210,7 +210,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:queue:rspec + bin/rake knapsack_pro:queue:rspec - name: Archive failed tests screenshots if: failure() @@ -268,7 +268,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests @@ -287,7 +287,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:queue:rspec + bin/rake knapsack_pro:queue:rspec - name: Archive failed tests screenshots if: failure() @@ -346,7 +346,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests @@ -365,7 +365,7 @@ jobs: KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec - name: Archive failed tests screenshots if: failure() @@ -424,7 +424,7 @@ jobs: - name: Set up database run: | - bundle exec rake db:create db:schema:load + bin/rake db:create db:schema:load - name: Run tests env: @@ -441,7 +441,7 @@ jobs: #KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}" run: | - bundle exec rake knapsack_pro:rspec + bin/rake knapsack_pro:rspec non_knapsack_jest_karma: runs-on: ubuntu-22.04 @@ -475,7 +475,7 @@ jobs: run: yarn install --frozen-lockfile - name: Run JS tests - run: bundle exec rake karma:run + run: bin/rake karma:run - name: Run jest tests run: yarn jest diff --git a/spec/system/admin/order_spec.rb b/spec/system/admin/order_spec.rb index b0b08badee..5a0ff97a0b 100644 --- a/spec/system/admin/order_spec.rb +++ b/spec/system/admin/order_spec.rb @@ -1140,165 +1140,4 @@ RSpec.describe ' end end end - - describe "Legal Invoices", feature: :invoices do - before do - login_as user - end - - describe "for order states" do - context "complete" do - let!(:order1) { - create(:order_with_totals_and_distribution, user:, distributor:, - order_cycle:, state: 'complete', - payment_state: 'balance_due', - customer_id: customer.id) - } - - context "editing the order" do - before do - visit spree.edit_admin_order_path(order1) - end - - it "displays the invoice tab" do - expect(page).to have_content "Complete".upcase - expect(page).to have_content "Invoices".upcase - end - end - - context "visiting the invoices tab" do - let!(:table_header) { - [ - "Date/Time", - "Invoice Number", - "Amount", - "Status", - "File", - ].join(" ").upcase - } - - let(:invoice_number){ "#{order.distributor_id}-1" } - let(:table_contents) { - [ - Invoice.first.created_at.strftime('%B %d, %Y').to_s, - invoice_number, - "0.0", - "Active", - "Download" - ].join(" ") - } - let(:download_href) { - "#{spree.print_admin_order_path(order1)}?invoice_id=#{Invoice.last.id}" - } - - before do - Spree::Config[:enterprise_number_required_on_invoices?] = false - visit spree.admin_order_invoices_path(order1) - end - - it "displays the invoices table" do - # with no invoices, only the table header is displayed - expect(page).to have_css "table.index" - expect(page).to have_content "#{customer.first_name} #{customer.last_name} -" - expect(page.find("table").text).to have_content(table_header) - - # the New invoice button + the warning should be visible - expect(page).to have_link "Create or Update Invoice" - expect(page).to have_content "The order has changed since the last invoice update." - click_link "Create or Update Invoice" - - # and disappear after clicking - expect(page).not_to have_link "Create or Update Invoice" - expect(page).not_to have_content "The order has changed since the last invoice update." - - # creating an invoice, displays a second row - expect(page.find("table").text).to have_content(table_contents) - - # with a valid invoice download link - expect(page).to have_link("Download", - href: download_href) - end - - context "the Create or Update Invoice button" do - context "when an ABN number is mandatory for invoices but not present" do - before do - Spree::Config[:enterprise_number_required_on_invoices?] = true - end - - it "displays a warning that an ABN is required when it's clicked" do - visit spree.admin_order_invoices_path(order1) - message = accept_prompt { click_link "Create or Update Invoice" } - distributor = order1.distributor - expect(message) - .to eq "#{distributor.name} must have a valid ABN before invoices can be used." - end - end - end - end - end - - context "resumed" do - let!(:order2) { - create(:order_with_totals_and_distribution, user:, distributor:, - order_cycle:, state: 'resumed', - payment_state: 'balance_due') - } - before do - visit spree.edit_admin_order_path(order2) - end - - it "displays the invoice tab" do - expect(page).to have_content "Resumed".upcase - expect(page).to have_content "Invoices".upcase - end - end - - context "canceled" do - let!(:order3) { - create(:order_with_totals_and_distribution, user:, distributor:, - order_cycle:, state: 'canceled', - payment_state: 'balance_due') - } - before do - visit spree.edit_admin_order_path(order3) - end - - it "displays the invoice tab" do - expect(page).to have_content "Cancelled".upcase - expect(page).to have_content "Invoices".upcase - end - end - - context "cart" do - let!(:order_empty) { - create(:order_with_line_items, user:, distributor:, order_cycle:, - line_items_count: 0) - } - before do - visit spree.edit_admin_order_path(order_empty) - end - - it "should not display the invoice tab" do - expect(page).to have_content "Cart".upcase - expect(page).not_to have_content "Invoices".upcase - end - end - - context "payment" do - let!(:order4) do - create(:order_ready_for_payment, user:, distributor:, - order_cycle:, - payment_state: 'balance_due') - end - before do - visit spree.edit_admin_order_path(order4) - end - - it "should not display the invoice tab" do - expect(page).to have_content "Payment".upcase - expect(page).not_to have_content "Invoices".upcase - end - end - end - end end diff --git a/spec/system/admin/orders/bulk_actions_spec.rb b/spec/system/admin/orders/bulk_actions_spec.rb new file mode 100644 index 0000000000..e93cfe4b5f --- /dev/null +++ b/spec/system/admin/orders/bulk_actions_spec.rb @@ -0,0 +1,473 @@ +# frozen_string_literal: true + +require "system_helper" + +RSpec.describe ' + As an administrator + I want to perform bulk order actions +' do + include AuthenticationHelper + include WebHelper + + let(:owner) { create(:user) } + let(:owner2) { create(:user) } + let(:customer) { create(:user) } + let(:customer2) { create(:user) } + let(:customer3) { create(:user) } + let(:customer4) { create(:user) } + let(:customer5) { create(:user) } + let(:billing_address) { create(:address, :randomized) } + let(:billing_address2) { create(:address, :randomized) } + let(:billing_address3) { create(:address, :randomized) } + let(:billing_address4) { create(:address, :randomized) } + let(:billing_address5) { create(:address, :randomized) } + let(:product) { create(:simple_product) } + let(:distributor) { + create(:distributor_enterprise, owner:, with_payment_and_shipping: true, + charges_sales_tax: true) + } + let(:distributor2) { create(:distributor_enterprise_with_tax, owner:) } + let(:distributor3) { + create(:distributor_enterprise, owner:, with_payment_and_shipping: true, + charges_sales_tax: true) + } + let(:distributor4) { + create(:distributor_enterprise, owner:, with_payment_and_shipping: true, + charges_sales_tax: true) + } + let(:distributor5) { create(:distributor_enterprise, owner: owner2, charges_sales_tax: true) } + let!(:shipping_method) { + create(:shipping_method_with, :pickup, name: "pick_up", + distributors: [distributor, distributor2, distributor3]) + } + let!(:shipping_method2) { + create(:shipping_method_with, :pickup, name: "delivery", + distributors: [distributor4, distributor5]) + } + let(:order_cycle) do + create(:simple_order_cycle, name: 'One', distributors: [distributor, distributor2, + distributor3, distributor4], + variants: [product.variants.first]) + end + + context "with a complete order" do + let(:order) do + create(:order_with_totals_and_distribution, user: customer, distributor:, + order_cycle:, + state: 'complete', payment_state: 'balance_due', + bill_address_id: billing_address.id) + end + + let!(:order_cycle2) { + create(:simple_order_cycle, name: 'Two', orders_close_at: 2.weeks.from_now) + } + let!(:order_cycle3) { + create(:simple_order_cycle, name: 'Three', orders_close_at: 3.weeks.from_now) + } + let!(:order_cycle4) { + create(:simple_order_cycle, name: 'Four', orders_close_at: 4.weeks.from_now) + } + let!(:order_cycle5) do + create(:simple_order_cycle, name: 'Five', coordinator: distributor5, + distributors: [distributor5], variants: [product.variants.first]) + end + + let!(:order2) { + create(:order_ready_to_ship, user: customer2, distributor: distributor2, + order_cycle: order_cycle2, completed_at: 2.days.ago, + bill_address_id: billing_address2.id) + } + let!(:order3) { + create(:order_with_credit_payment, user: customer3, distributor: distributor3, + order_cycle: order_cycle3, + bill_address_id: billing_address3.id) + } + let!(:order4) { + create(:order_with_credit_payment, user: customer4, distributor: distributor4, + order_cycle: order_cycle4, + bill_address_id: billing_address4.id) + } + let!(:order5) { + create(:order_ready_to_ship, user: customer5, distributor: distributor5, + order_cycle: order_cycle5, + bill_address_id: billing_address5.id) + } + + context "as a super admin" do + before do + login_as_admin + visit spree.admin_orders_path + end + + context "can bulk send invoices per email" do + before do + Spree::Config[:enable_invoices?] = true + Spree::Config[:enterprise_number_required_on_invoices?] = false + end + + context "with multiple orders with differents states" do + before do + order2.update(state: "complete") + order3.update(state: "resumed") + order4.update(state: "canceled") + order5.update(state: "payment") + end + + it "can bulk send invoices per email, but only for the 'complete' or 'resumed' ones" do + within "#listing_orders" do + page.find("input[name='bulk_ids[]'][value='#{order2.id}']").click + page.find("input[name='bulk_ids[]'][value='#{order3.id}']").click + page.find("input[name='bulk_ids[]'][value='#{order4.id}']").click + page.find("input[name='bulk_ids[]'][value='#{order5.id}']").click + end + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Send Invoices").click + end + + expect(page).to have_content "This will email customer invoices " \ + "for all selected complete orders." + expect(page).to have_content "Are you sure you want to proceed?" + + within ".reveal-modal" do + expect { + find_button("Confirm").click + }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + expect(page).to have_content "Invoice emails sent for 2 orders." + end + end + + it "can bulk send confirmation email from 2 orders" do + page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click + page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Resend Confirmation").click + end + + expect(page).to have_content "Are you sure you want to proceed?" + + within ".reveal-modal" do + expect { + find_button("Confirm").click + }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + expect(page).to have_content "Confirmation emails sent for 2 orders." + end + end + + context "can bulk print invoices" do + let(:order4_selector){ "#order_#{order4.id} input[name='bulk_ids[]']" } + let(:order5_selector){ "#order_#{order5.id} input[name='bulk_ids[]']" } + + shared_examples "can bulk print invoices from 2 orders" do + it "bulk prints invoices in pdf format" do + page.find(order4_selector).click + page.find(order5_selector).click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.to enqueue_job(BulkInvoiceJob).exactly(:once) + end + + expect(page).to have_content "Compiling Invoices" + expect(page).to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + # we don't run Sidekiq in test environment, so we need to manually run enqueued jobs + # to generate PDF files, and change the modal accordingly + perform_enqueued_jobs(only: BulkInvoiceJob) + + expect(page).to have_content "Bulk Invoice created" + + within ".modal-content" do + expect(page).to have_link(class: "button", text: "VIEW FILE", href: /invoices/) + + invoice_content = extract_pdf_content + + expect(invoice_content).to have_content("TAX INVOICE", count: 2) + expect(invoice_content).to have_content(order4.number.to_s) + expect(invoice_content).to have_content(order5.number.to_s) + expect(invoice_content).to have_content(distributor4.name.to_s) + expect(invoice_content).to have_content(distributor5.name.to_s) + expect(invoice_content).to have_content(order_cycle4.name.to_s) + expect(invoice_content).to have_content(order_cycle5.name.to_s) + end + end + end + + shared_examples "should ignore the non invoiceable order" do + it "bulk prints invoices in pdf format" do + page.find(order4_selector).click + page.find(order5_selector).click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.to enqueue_job(BulkInvoiceJob).exactly(:once) + end + + expect(page).to have_content "Compiling Invoices" + expect(page).to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + perform_enqueued_jobs(only: BulkInvoiceJob) + + expect(page).to have_content "Bulk Invoice created" + + within ".modal-content" do + expect(page).to have_link(class: "button", text: "VIEW FILE", + href: /invoices/) + + invoice_content = extract_pdf_content + + expect(invoice_content).to have_content("TAX INVOICE", count: 1) + expect(invoice_content).not_to have_content(order4.number.to_s) + expect(invoice_content).to have_content(order5.number.to_s) + expect(invoice_content).not_to have_content(distributor4.name.to_s) + expect(invoice_content).to have_content(distributor5.name.to_s) + expect(invoice_content).not_to have_content(order_cycle4.name.to_s) + expect(invoice_content).to have_content(order_cycle5.name.to_s) + end + end + end + + context "ABN is not required" do + before do + allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) + .and_return false + end + + it_behaves_like "can bulk print invoices from 2 orders" + + context "with legal invoices feature", feature: :invoices do + it_behaves_like "can bulk print invoices from 2 orders" + end + + context "one of the two orders is not invoiceable" do + before do + order4.cancel! + end + + it_behaves_like "should ignore the non invoiceable order" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "should ignore the non invoiceable order" + end + end + + context "ordering by customer name" do + context "ascending" do + let!(:surnames) { + [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), + order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort + } + it "orders by customer name ascending" do + page.find('a', text: "NAME").click # orders alphabetically (asc) + sleep(0.5) # waits for column sorting + + page.find("#selectAll").click + + print_all_invoices + + invoice_content = extract_pdf_content + + expect( + invoice_content.join + ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) + end + end + context "descending" do + let!(:surnames) { + [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), + order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort.reverse + } + it "order by customer name descending" do + page.find('a', text: "NAME").click # orders alphabetically (asc) + sleep(0.5) # waits for column sorting + page.find('a', text: "NAME").click # orders alphabetically (desc) + sleep(0.5) # waits for column sorting + + page.find("#selectAll").click + + print_all_invoices + + invoice_content = extract_pdf_content + + expect( + invoice_content.join + ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) + end + end + end + end + context "ABN is required" do + before do + allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) + .and_return true + end + context "All the distributors setup the ABN" do + before do + order4.distributor.update(abn: "123456789") + order5.distributor.update(abn: "987654321") + end + context "all the orders are invoiceable (completed/resumed)" do + it_behaves_like "can bulk print invoices from 2 orders" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "can bulk print invoices from 2 orders" + end + end + + context "one of the two orders is not invoiceable" do + before do + order4.cancel! + end + + it_behaves_like "should ignore the non invoiceable order" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "should ignore the non invoiceable order" + end + end + end + context "the distributor of one of the order didn't set the ABN" do + before do + order4.distributor.update(abn: "123456789") + order5.distributor.update(abn: nil) + end + + shared_examples "should not print the invoice" do + it "should render a warning message" do + page.find(order4_selector).click + page.find(order5_selector).click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.not_to enqueue_job(BulkInvoiceJob) + end + + expect(page).not_to have_content "Compiling Invoices" + expect(page).not_to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + expect(page).to have_content "#{ + order5.distributor.name + } must have a valid ABN before invoices can be used." + end + end + it_behaves_like "should not print the invoice" + context "with legal invoices feature", feature: :invoices do + it_behaves_like "should not print the invoice" + end + end + end + end + it "can bulk cancel 2 orders" do + page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click + page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Cancel Orders").click + end + + expect(page).to have_content "Are you sure you want to proceed?" + expect(page).to have_content "This will cancel the current order." + + within ".reveal-modal" do + uncheck "Send a cancellation email to the customer" + expect { + find_button("Cancel").click # Cancels the cancel action + }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Cancel Orders").click + end + + within ".reveal-modal" do + expect { + find_button("Confirm").click # Confirms the cancel action + }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) + end + + expect(page).to have_content("CANCELLED", count: 2) + end + end + + context "for a hub manager" do + before do + login_as owner2 + visit spree.admin_orders_path + end + + it "displays the orders for the respective distributor" do + expect(page).to have_content order5.number # displays the only order for distributor5 + expect(page).not_to have_content order.number + expect(page).not_to have_content order2.number + expect(page).not_to have_content order3.number + expect(page).not_to have_content order4.number + end + + it "cannot send emails to orders if permission have been revoked in the meantime" do + page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click + # Find the clicked order + order = Spree::Order.find_by( + id: page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").value + ) + # Revoke permission for the current user on that specific order by changing its owners + order.update_attribute(:distributor, distributor) + order.update_attribute(:order_cycle, order_cycle) + + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + page.find("span", text: "Resend Confirmation").click + end + + expect(page).to have_content "Are you sure you want to proceed?" + + within ".reveal-modal" do + expect { + find_button("Confirm").click + }.not_to enqueue_job(ActionMailer::MailDeliveryJob) + end + end + end + end + + def extract_pdf_content + # Extract last part of invoice URL + link = page.find(class: "button", text: "VIEW FILE") + filename = link[:href].match %r{/invoices/.*} + + # Load invoice temp file directly instead of downloading + reader = PDF::Reader.new("tmp/#{filename}.pdf") + reader.pages.map(&:text) + end + + def print_all_invoices + page.find("span.icon-reorder", text: "ACTIONS").click + within ".ofn-drop-down .menu" do + expect { + page.find("span", text: "Print Invoices").click # Prints invoices in bulk + }.to enqueue_job(BulkInvoiceJob).exactly(:once) + end + + expect(page).to have_content "Compiling Invoices" + expect(page).to have_content "Please wait until the PDF is ready " \ + "before closing this modal." + + perform_enqueued_jobs(only: BulkInvoiceJob) + + expect(page).to have_content "Bulk Invoice created" + end +end diff --git a/spec/system/admin/orders/invoices_spec.rb b/spec/system/admin/orders/invoices_spec.rb index b5ade9edc9..b101a28a7a 100644 --- a/spec/system/admin/orders/invoices_spec.rb +++ b/spec/system/admin/orders/invoices_spec.rb @@ -211,3 +211,178 @@ RSpec.describe ' end end end + +RSpec.describe "Invoice order states", feature: :invoices do + let(:user) { create(:user) } + let(:product) { create(:simple_product) } + let(:distributor) { create(:distributor_enterprise, owner: user, 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:, + order_cycle:, state: 'complete', + payment_state: 'balance_due') + end + let(:customer) { order.customer } + + before do + order.finalize! + + login_as user + end + + context "complete" do + let!(:order1) { + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'complete', + payment_state: 'balance_due', + customer_id: customer.id) + } + + context "editing the order" do + before do + visit spree.edit_admin_order_path(order1) + end + + it "displays the invoice tab" do + expect(page).to have_content "Complete".upcase + expect(page).to have_content "Invoices".upcase + end + end + + context "visiting the invoices tab" do + let!(:table_header) { + [ + "Date/Time", + "Invoice Number", + "Amount", + "Status", + "File", + ].join(" ").upcase + } + + let(:invoice_number){ "#{order.distributor_id}-1" } + let(:table_contents) { + [ + Invoice.first.created_at.strftime('%B %d, %Y').to_s, + invoice_number, + "0.0", + "Active", + "Download" + ].join(" ") + } + let(:download_href) { + "#{spree.print_admin_order_path(order1)}?invoice_id=#{Invoice.last.id}" + } + + before do + Spree::Config[:enterprise_number_required_on_invoices?] = false + visit spree.admin_order_invoices_path(order1) + end + + it "displays the invoices table" do + # with no invoices, only the table header is displayed + expect(page).to have_css "table.index" + expect(page).to have_content "#{customer.first_name} #{customer.last_name} -" + expect(page.find("table").text).to have_content(table_header) + + # the New invoice button + the warning should be visible + expect(page).to have_link "Create or Update Invoice" + expect(page).to have_content "The order has changed since the last invoice update." + click_link "Create or Update Invoice" + + # and disappear after clicking + expect(page).not_to have_link "Create or Update Invoice" + expect(page).not_to have_content "The order has changed since the last invoice update." + + # creating an invoice, displays a second row + expect(page.find("table").text).to have_content(table_contents) + + # with a valid invoice download link + expect(page).to have_link("Download", + href: download_href) + end + + context "the Create or Update Invoice button" do + context "when an ABN number is mandatory for invoices but not present" do + before do + Spree::Config[:enterprise_number_required_on_invoices?] = true + end + + it "displays a warning that an ABN is required when it's clicked" do + visit spree.admin_order_invoices_path(order1) + message = accept_prompt { click_link "Create or Update Invoice" } + distributor = order1.distributor + expect(message) + .to eq "#{distributor.name} must have a valid ABN before invoices can be used." + end + end + end + end + end + + context "resumed" do + let!(:order2) { + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'resumed', + payment_state: 'balance_due') + } + before do + visit spree.edit_admin_order_path(order2) + end + + it "displays the invoice tab" do + expect(page).to have_content "Resumed".upcase + expect(page).to have_content "Invoices".upcase + end + end + + context "canceled" do + let!(:order3) { + create(:order_with_totals_and_distribution, user:, distributor:, + order_cycle:, state: 'canceled', + payment_state: 'balance_due') + } + before do + visit spree.edit_admin_order_path(order3) + end + + it "displays the invoice tab" do + expect(page).to have_content "Cancelled".upcase + expect(page).to have_content "Invoices".upcase + end + end + + context "cart" do + let!(:order_empty) { + create(:order_with_line_items, user:, distributor:, order_cycle:, + line_items_count: 0) + } + before do + visit spree.edit_admin_order_path(order_empty) + end + + it "should not display the invoice tab" do + expect(page).to have_content "Cart".upcase + expect(page).not_to have_content "Invoices".upcase + end + end + + context "payment" do + let!(:order4) do + create(:order_ready_for_payment, user:, distributor:, + order_cycle:, + payment_state: 'balance_due') + end + before do + visit spree.edit_admin_order_path(order4) + end + + it "should not display the invoice tab" do + expect(page).to have_content "Payment".upcase + expect(page).not_to have_content "Invoices".upcase + end + end +end diff --git a/spec/system/admin/orders_spec.rb b/spec/system/admin/orders_spec.rb index a2ce6a82a0..deedf07e3f 100644 --- a/spec/system/admin/orders_spec.rb +++ b/spec/system/admin/orders_spec.rb @@ -504,358 +504,6 @@ RSpec.describe ' end end - context "bulk actions" do - context "as a super admin" do - before do - login_as_admin - visit spree.admin_orders_path - end - - context "can bulk send invoices per email" do - before do - Spree::Config[:enable_invoices?] = true - Spree::Config[:enterprise_number_required_on_invoices?] = false - end - - context "with multiple orders with differents states" do - before do - order2.update(state: "complete") - order3.update(state: "resumed") - order4.update(state: "canceled") - order5.update(state: "payment") - end - - it "can bulk send invoices per email, but only for the 'complete' or 'resumed' ones" do - within "#listing_orders" do - page.find("input[name='bulk_ids[]'][value='#{order2.id}']").click - page.find("input[name='bulk_ids[]'][value='#{order3.id}']").click - page.find("input[name='bulk_ids[]'][value='#{order4.id}']").click - page.find("input[name='bulk_ids[]'][value='#{order5.id}']").click - end - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Send Invoices").click - end - - expect(page).to have_content "This will email customer invoices " \ - "for all selected complete orders." - expect(page).to have_content "Are you sure you want to proceed?" - - within ".reveal-modal" do - expect { - find_button("Confirm").click - }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - expect(page).to have_content "Invoice emails sent for 2 orders." - end - end - - it "can bulk send confirmation email from 2 orders" do - page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click - page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Resend Confirmation").click - end - - expect(page).to have_content "Are you sure you want to proceed?" - - within ".reveal-modal" do - expect { - find_button("Confirm").click - }.to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - expect(page).to have_content "Confirmation emails sent for 2 orders." - end - end - - context "can bulk print invoices" do - let(:order4_selector){ "#order_#{order4.id} input[name='bulk_ids[]']" } - let(:order5_selector){ "#order_#{order5.id} input[name='bulk_ids[]']" } - - shared_examples "can bulk print invoices from 2 orders" do - it "bulk prints invoices in pdf format" do - page.find(order4_selector).click - page.find(order5_selector).click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.to enqueue_job(BulkInvoiceJob).exactly(:once) - end - - expect(page).to have_content "Compiling Invoices" - expect(page).to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - # we don't run Sidekiq in test environment, so we need to manually run enqueued jobs - # to generate PDF files, and change the modal accordingly - perform_enqueued_jobs(only: BulkInvoiceJob) - - expect(page).to have_content "Bulk Invoice created" - - within ".modal-content" do - expect(page).to have_link(class: "button", text: "VIEW FILE", href: /invoices/) - - invoice_content = extract_pdf_content - - expect(invoice_content).to have_content("TAX INVOICE", count: 2) - expect(invoice_content).to have_content(order4.number.to_s) - expect(invoice_content).to have_content(order5.number.to_s) - expect(invoice_content).to have_content(distributor4.name.to_s) - expect(invoice_content).to have_content(distributor5.name.to_s) - expect(invoice_content).to have_content(order_cycle4.name.to_s) - expect(invoice_content).to have_content(order_cycle5.name.to_s) - end - end - end - - shared_examples "should ignore the non invoiceable order" do - it "bulk prints invoices in pdf format" do - page.find(order4_selector).click - page.find(order5_selector).click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.to enqueue_job(BulkInvoiceJob).exactly(:once) - end - - expect(page).to have_content "Compiling Invoices" - expect(page).to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - perform_enqueued_jobs(only: BulkInvoiceJob) - - expect(page).to have_content "Bulk Invoice created" - - within ".modal-content" do - expect(page).to have_link(class: "button", text: "VIEW FILE", - href: /invoices/) - - invoice_content = extract_pdf_content - - expect(invoice_content).to have_content("TAX INVOICE", count: 1) - expect(invoice_content).not_to have_content(order4.number.to_s) - expect(invoice_content).to have_content(order5.number.to_s) - expect(invoice_content).not_to have_content(distributor4.name.to_s) - expect(invoice_content).to have_content(distributor5.name.to_s) - expect(invoice_content).not_to have_content(order_cycle4.name.to_s) - expect(invoice_content).to have_content(order_cycle5.name.to_s) - end - end - end - - context "ABN is not required" do - before do - allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) - .and_return false - end - - it_behaves_like "can bulk print invoices from 2 orders" - - context "with legal invoices feature", feature: :invoices do - it_behaves_like "can bulk print invoices from 2 orders" - end - - context "one of the two orders is not invoiceable" do - before do - order4.cancel! - end - - it_behaves_like "should ignore the non invoiceable order" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "should ignore the non invoiceable order" - end - end - - context "ordering by customer name" do - context "ascending" do - let!(:surnames) { - [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), - order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort - } - it "orders by customer name ascending" do - page.find('a', text: "NAME").click # orders alphabetically (asc) - sleep(0.5) # waits for column sorting - - page.find("#selectAll").click - - print_all_invoices - - invoice_content = extract_pdf_content - - expect( - invoice_content.join - ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) - end - end - context "descending" do - let!(:surnames) { - [order2.name.gsub(/.* /, ""), order3.name.gsub(/.* /, ""), - order4.name.gsub(/.* /, ""), order5.name.gsub(/.* /, "")].sort.reverse - } - it "order by customer name descending" do - page.find('a', text: "NAME").click # orders alphabetically (asc) - sleep(0.5) # waits for column sorting - page.find('a', text: "NAME").click # orders alphabetically (desc) - sleep(0.5) # waits for column sorting - - page.find("#selectAll").click - - print_all_invoices - - invoice_content = extract_pdf_content - - expect( - invoice_content.join - ).to match(/#{surnames[0]}.*#{surnames[1]}.*#{surnames[2]}.*#{surnames[3]}/m) - end - end - end - end - context "ABN is required" do - before do - allow(Spree::Config).to receive(:enterprise_number_required_on_invoices?) - .and_return true - end - context "All the distributors setup the ABN" do - before do - order4.distributor.update(abn: "123456789") - order5.distributor.update(abn: "987654321") - end - context "all the orders are invoiceable (completed/resumed)" do - it_behaves_like "can bulk print invoices from 2 orders" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "can bulk print invoices from 2 orders" - end - end - - context "one of the two orders is not invoiceable" do - before do - order4.cancel! - end - - it_behaves_like "should ignore the non invoiceable order" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "should ignore the non invoiceable order" - end - end - end - context "the distributor of one of the order didn't set the ABN" do - before do - order4.distributor.update(abn: "123456789") - order5.distributor.update(abn: nil) - end - - shared_examples "should not print the invoice" do - it "should render a warning message" do - page.find(order4_selector).click - page.find(order5_selector).click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.not_to enqueue_job(BulkInvoiceJob) - end - - expect(page).not_to have_content "Compiling Invoices" - expect(page).not_to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - expect(page).to have_content "#{ - order5.distributor.name - } must have a valid ABN before invoices can be used." - end - end - it_behaves_like "should not print the invoice" - context "with legal invoices feature", feature: :invoices do - it_behaves_like "should not print the invoice" - end - end - end - end - it "can bulk cancel 2 orders" do - page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click - page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Cancel Orders").click - end - - expect(page).to have_content "Are you sure you want to proceed?" - expect(page).to have_content "This will cancel the current order." - - within ".reveal-modal" do - uncheck "Send a cancellation email to the customer" - expect { - find_button("Cancel").click # Cancels the cancel action - }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Cancel Orders").click - end - - within ".reveal-modal" do - expect { - find_button("Confirm").click # Confirms the cancel action - }.not_to enqueue_job(ActionMailer::MailDeliveryJob).exactly(:twice) - end - - expect(page).to have_content("CANCELLED", count: 2) - end - end - - context "for a hub manager" do - before do - login_as owner2 - visit spree.admin_orders_path - end - - it "displays the orders for the respective distributor" do - expect(page).to have_content order5.number # displays the only order for distributor5 - expect(page).not_to have_content order.number - expect(page).not_to have_content order2.number - expect(page).not_to have_content order3.number - expect(page).not_to have_content order4.number - end - - it "cannot send emails to orders if permission have been revoked in the meantime" do - page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click - # Find the clicked order - order = Spree::Order.find_by( - id: page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").value - ) - # Revoke permission for the current user on that specific order by changing its owners - order.update_attribute(:distributor, distributor) - order.update_attribute(:order_cycle, order_cycle) - - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - page.find("span", text: "Resend Confirmation").click - end - - expect(page).to have_content "Are you sure you want to proceed?" - - within ".reveal-modal" do - expect { - find_button("Confirm").click - }.not_to enqueue_job(ActionMailer::MailDeliveryJob) - end - end - end - end - context "pagination" do before do login_as_admin @@ -1131,30 +779,4 @@ RSpec.describe ' expect(find("input.datepicker").value).to be_empty end end - def extract_pdf_content - # Extract last part of invoice URL - link = page.find(class: "button", text: "VIEW FILE") - filename = link[:href].match %r{/invoices/.*} - - # Load invoice temp file directly instead of downloading - reader = PDF::Reader.new("tmp/#{filename}.pdf") - reader.pages.map(&:text) - end - - def print_all_invoices - page.find("span.icon-reorder", text: "ACTIONS").click - within ".ofn-drop-down .menu" do - expect { - page.find("span", text: "Print Invoices").click # Prints invoices in bulk - }.to enqueue_job(BulkInvoiceJob).exactly(:once) - end - - expect(page).to have_content "Compiling Invoices" - expect(page).to have_content "Please wait until the PDF is ready " \ - "before closing this modal." - - perform_enqueued_jobs(only: BulkInvoiceJob) - - expect(page).to have_content "Bulk Invoice created" - end end