mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-04 22:16:08 +00:00
Merge branch 'master' into 12003_improve_error_messageg_when_no_products_are_changed
This commit is contained in:
@@ -383,9 +383,9 @@ GEM
|
||||
knapsack_pro (6.0.4)
|
||||
rake
|
||||
language_server-protocol (3.17.0.3)
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
letter_opener (1.8.1)
|
||||
launchy (2.5.2)
|
||||
addressable (~> 2.8)
|
||||
letter_opener (1.9.0)
|
||||
launchy (>= 2.2, < 3)
|
||||
link_header (0.0.8)
|
||||
listen (3.8.0)
|
||||
|
||||
@@ -23,6 +23,8 @@ module Api
|
||||
|
||||
OrderWorkflow.new(@order).advance_to_payment if @order.line_items.any?
|
||||
|
||||
@order.recreate_all_fees!
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
.omega.eight.columns
|
||||
= f.check_box :automatic_notifications
|
||||
|
||||
.row{ "data-controller": "remote-toggle", "data-remote-toggle-selector-value": "#advanced_settings" }
|
||||
.row{ "data-controller": "toggle-control", "data-toggle-control-selector-value": "#advanced_settings" }
|
||||
.sixteen.columns.alpha.omega.text-center
|
||||
%input{ type: 'submit', value: t('.save_reload') }
|
||||
%a{ href: "#", "data-action": "click->remote-toggle#toggle" }= t(:close)
|
||||
%a{ href: "#", "data-action": "click->toggle-control#toggleAdvancedSettings" }= t(:close)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
- content_for :page_actions do
|
||||
%li{ "data-controller": "remote-toggle", "data-remote-toggle-selector-value": "#advanced_settings" }
|
||||
%button#toggle_settings{ "data-action": "click->remote-toggle#toggle" }
|
||||
%li{ "data-controller": "toggle-control", "data-toggle-control-selector-value": "#advanced_settings" }
|
||||
%button#toggle_settings{ "data-action": "click->toggle-control#toggleAdvancedSettings" }
|
||||
= t('.advanced_settings')
|
||||
%i.icon-chevron-down{ "data-remote-toggle-target": "chevron" }
|
||||
%i.icon-chevron-down{ "data-toggle-control-target": "chevron" }
|
||||
|
||||
#advanced_settings{ style: "display: none" }
|
||||
= render partial: "/admin/order_cycles/advanced_settings"
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["chevron"];
|
||||
static values = { selector: String };
|
||||
|
||||
toggle(event) {
|
||||
if (this.hasChevronTarget) {
|
||||
this.chevronTarget.classList.toggle("icon-chevron-down");
|
||||
this.chevronTarget.classList.toggle("icon-chevron-up");
|
||||
}
|
||||
|
||||
const element = document.querySelector(this.selectorValue);
|
||||
element.style.display = element.style.display === "none" ? "block" : "none";
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["control", "content"];
|
||||
static targets = ["control", "content", "chevron"];
|
||||
static values = { selector: String };
|
||||
|
||||
disableIfPresent(event) {
|
||||
const input = event.currentTarget;
|
||||
@@ -32,7 +33,16 @@ export default class extends Controller {
|
||||
t.style.display = input.dataset.toggleShow === "true" ? "block" : "none";
|
||||
});
|
||||
}
|
||||
//todo: can toggleDisplay with optional chevron-target replace RemoteToggleController?
|
||||
|
||||
toggleAdvancedSettings(event) {
|
||||
if (this.hasChevronTarget) {
|
||||
this.chevronTarget.classList.toggle("icon-chevron-down");
|
||||
this.chevronTarget.classList.toggle("icon-chevron-up");
|
||||
}
|
||||
|
||||
const element = document.querySelector(this.selectorValue);
|
||||
element.style.display = element.style.display === "none" ? "block" : "none";
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
|
||||
@@ -38,13 +38,6 @@ module OrderManagement
|
||||
update_pending_payment
|
||||
end
|
||||
|
||||
def update_pending_payment
|
||||
return unless order.state.in? ["payment", "confirmation"]
|
||||
return unless order.pending_payments.any?
|
||||
|
||||
order.pending_payments.first.update_attribute :amount, order.total
|
||||
end
|
||||
|
||||
# Updates the following Order total values:
|
||||
#
|
||||
# - payment_total - total value of all finalized Payments (excludes non-finalized Payments)
|
||||
@@ -239,6 +232,16 @@ module OrderManagement
|
||||
def requires_authorization?
|
||||
payments.requires_authorization.any? && payments.completed.empty?
|
||||
end
|
||||
|
||||
def update_pending_payment
|
||||
# We only want to update complete order pending payment when it's a cash payment. We assume
|
||||
# that if the payment was a credit card it would alread have been processed, so we don't
|
||||
# bother checking the payment type
|
||||
return unless order.state.in? ["payment", "confirmation", "complete"]
|
||||
return unless order.pending_payments.any?
|
||||
|
||||
order.pending_payments.first.update_attribute :amount, order.total
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,7 +8,7 @@ module OrderManagement
|
||||
let(:order) { create(:order) }
|
||||
let(:updater) { OrderManagement::Order::Updater.new(order) }
|
||||
|
||||
context "updating order totals" do
|
||||
describe "#update_totals" do
|
||||
before do
|
||||
2.times { create(:line_item, order:, price: 10) }
|
||||
end
|
||||
@@ -19,11 +19,23 @@ module OrderManagement
|
||||
updater.update_totals
|
||||
expect(order.payment_total).to eq(10)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_item_total" do
|
||||
before do
|
||||
2.times { create(:line_item, order:, price: 10) }
|
||||
end
|
||||
|
||||
it "updates item total" do
|
||||
updater.update_item_total
|
||||
expect(order.item_total).to eq(20)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_adjustment_total" do
|
||||
before do
|
||||
2.times { create(:line_item, order:, price: 10) }
|
||||
end
|
||||
|
||||
it "updates adjustment totals" do
|
||||
allow(order).to receive_message_chain(:all_adjustments, :additional, :eligible,
|
||||
@@ -40,7 +52,7 @@ module OrderManagement
|
||||
end
|
||||
end
|
||||
|
||||
context "updating shipment state" do
|
||||
describe "#update_shipment_state" do
|
||||
let(:shipment) { build(:shipment) }
|
||||
|
||||
before do
|
||||
@@ -85,19 +97,111 @@ module OrderManagement
|
||||
order.state_changed('shipment')
|
||||
end
|
||||
|
||||
context "completed order" do
|
||||
describe "#update" do
|
||||
it "updates totals once" do
|
||||
expect(updater).to receive(:update_totals).once
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "updates all adjustments" do
|
||||
expect(updater).to receive(:update_all_adjustments)
|
||||
updater.update
|
||||
end
|
||||
|
||||
context "completed order" do
|
||||
before { allow(order).to receive(:completed?) { true } }
|
||||
|
||||
it "updates payment state" do
|
||||
expect(updater).to receive(:update_payment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "updates shipment state" do
|
||||
expect(updater).to receive(:update_shipment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
context "whith pending payments" do
|
||||
let(:order) { create(:completed_order_with_totals) }
|
||||
|
||||
it "updates pending payments" do
|
||||
payment = create(:payment, order:, amount: order.total)
|
||||
|
||||
# update order so the order total will change
|
||||
update_order_quantity(order)
|
||||
order.payments.reload
|
||||
|
||||
expect { updater.update }.to change { payment.reload.amount }.from(50).to(60)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "incompleted order" do
|
||||
before { allow(order).to receive_messages completed?: false }
|
||||
|
||||
it "doesnt update payment state" do
|
||||
expect(updater).not_to receive(:update_payment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "doesnt update shipment state" do
|
||||
expect(updater).not_to receive(:update_shipment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "doesnt update the order shipment" do
|
||||
shipment = build(:shipment)
|
||||
allow(order).to receive_messages shipments: [shipment]
|
||||
|
||||
expect(shipment).not_to receive(:update!).with(order)
|
||||
expect(updater).not_to receive(:update_shipments).with(order)
|
||||
updater.update
|
||||
end
|
||||
|
||||
context "with pending payments" do
|
||||
let!(:payment) { create(:payment, order:, amount: order.total) }
|
||||
|
||||
context "with order in payment state" do
|
||||
let(:order) { create(:order_with_totals, state: "payment") }
|
||||
|
||||
it "updates pending payments" do
|
||||
# update order so the order total will change
|
||||
update_order_quantity(order)
|
||||
order.payments.reload
|
||||
|
||||
expect { updater.update }.to change { payment.reload.amount }.from(10).to(20)
|
||||
end
|
||||
end
|
||||
|
||||
context "with order in confirmation state" do
|
||||
let(:order) { create(:order_with_totals, state: "confirmation") }
|
||||
|
||||
it "updates pending payments" do
|
||||
# update order so the order total will change
|
||||
update_order_quantity(order)
|
||||
order.payments.reload
|
||||
|
||||
expect { updater.update }.to change { payment.reload.amount }.from(10).to(20)
|
||||
end
|
||||
end
|
||||
|
||||
context "with order in cart" do
|
||||
let(:order) { create(:order_with_totals) }
|
||||
|
||||
it "doesn't update pending payments" do
|
||||
# update order so the order total will change
|
||||
update_order_quantity(order)
|
||||
|
||||
expect { updater.update }.not_to change { payment.reload.amount }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_shipments" do
|
||||
before { allow(order).to receive(:completed?) { true } }
|
||||
|
||||
it "updates payment state" do
|
||||
expect(updater).to receive(:update_payment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "updates shipment state" do
|
||||
expect(updater).to receive(:update_shipment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "updates the order shipment" do
|
||||
shipment = build(:shipment)
|
||||
allow(order).to receive_messages shipments: [shipment]
|
||||
@@ -107,39 +211,6 @@ module OrderManagement
|
||||
end
|
||||
end
|
||||
|
||||
context "incompleted order" do
|
||||
before { allow(order).to receive_messages completed?: false }
|
||||
|
||||
it "doesnt update payment state" do
|
||||
expect(updater).not_to receive(:update_payment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "doesnt update shipment state" do
|
||||
expect(updater).not_to receive(:update_shipment_state)
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "doesnt update the order shipment" do
|
||||
shipment = build(:shipment)
|
||||
allow(order).to receive_messages shipments: [shipment]
|
||||
|
||||
expect(shipment).not_to receive(:update!).with(order)
|
||||
expect(updater).not_to receive(:update_shipments).with(order)
|
||||
updater.update
|
||||
end
|
||||
end
|
||||
|
||||
it "updates totals once" do
|
||||
expect(updater).to receive(:update_totals).once
|
||||
updater.update
|
||||
end
|
||||
|
||||
it "updates all adjustments" do
|
||||
expect(updater).to receive(:update_all_adjustments)
|
||||
updater.update
|
||||
end
|
||||
|
||||
describe "#update_payment_state" do
|
||||
context "when the order has no valid payments" do
|
||||
it "is failed" do
|
||||
@@ -277,9 +348,28 @@ module OrderManagement
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when unused payments records exist which require authorization, " \
|
||||
"but the order is fully paid" do
|
||||
let!(:cash_payment) {
|
||||
build(:payment, state: "completed", amount: order.new_outstanding_balance)
|
||||
}
|
||||
let!(:stripe_payment) { build(:payment, state: "requires_authorization") }
|
||||
before do
|
||||
order.payments << cash_payment
|
||||
order.payments << stripe_payment
|
||||
end
|
||||
|
||||
it "cancels unused payments requiring authorization" do
|
||||
expect(stripe_payment).to receive(:void_transaction!)
|
||||
expect(cash_payment).to_not receive(:void_transaction!)
|
||||
|
||||
order.updater.update_payment_state
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#shipping_address_from_distributor' do
|
||||
describe '#shipping_address_from_distributor' do
|
||||
let(:distributor) { build(:distributor_enterprise) }
|
||||
let(:shipment) {
|
||||
create(:shipment_with, :shipping_method, shipping_method:)
|
||||
@@ -313,69 +403,52 @@ module OrderManagement
|
||||
end
|
||||
end
|
||||
|
||||
describe "updating order totals" do
|
||||
describe "#update_totals_and_states" do
|
||||
it "deals with legacy taxes" do
|
||||
expect(updater).to receive(:handle_legacy_taxes)
|
||||
describe "#update_totals_and_states" do
|
||||
it "deals with legacy taxes" do
|
||||
expect(updater).to receive(:handle_legacy_taxes)
|
||||
|
||||
updater.update_totals_and_states
|
||||
updater.update_totals_and_states
|
||||
end
|
||||
end
|
||||
|
||||
describe "#handle_legacy_taxes" do
|
||||
context "when the order is incomplete" do
|
||||
it "doesn't touch taxes" do
|
||||
allow(order).to receive(:completed?) { false }
|
||||
|
||||
expect(order).to_not receive(:create_tax_charge!)
|
||||
updater.__send__(:handle_legacy_taxes)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#handle_legacy_taxes" do
|
||||
context "when the order is incomplete" do
|
||||
it "doesn't touch taxes" do
|
||||
allow(order).to receive(:completed?) { false }
|
||||
context "when the order is complete" do
|
||||
before { allow(order).to receive(:completed?) { true } }
|
||||
|
||||
context "and the order has legacy taxes" do
|
||||
let!(:legacy_tax_adjustment) {
|
||||
create(:adjustment, order:, adjustable: order, included: false,
|
||||
originator_type: "Spree::TaxRate")
|
||||
}
|
||||
|
||||
it "re-applies order taxes" do
|
||||
expect(order).to receive(:create_tax_charge!)
|
||||
|
||||
expect(order).to_not receive(:create_tax_charge!)
|
||||
updater.__send__(:handle_legacy_taxes)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the order is complete" do
|
||||
before { allow(order).to receive(:completed?) { true } }
|
||||
context "and the order has no legacy taxes" do
|
||||
it "leaves taxes untouched" do
|
||||
expect(order).to_not receive(:create_tax_charge!)
|
||||
|
||||
context "and the order has legacy taxes" do
|
||||
let!(:legacy_tax_adjustment) {
|
||||
create(:adjustment, order:, adjustable: order, included: false,
|
||||
originator_type: "Spree::TaxRate")
|
||||
}
|
||||
|
||||
it "re-applies order taxes" do
|
||||
expect(order).to receive(:create_tax_charge!)
|
||||
|
||||
updater.__send__(:handle_legacy_taxes)
|
||||
end
|
||||
end
|
||||
|
||||
context "and the order has no legacy taxes" do
|
||||
it "leaves taxes untouched" do
|
||||
expect(order).to_not receive(:create_tax_charge!)
|
||||
|
||||
updater.__send__(:handle_legacy_taxes)
|
||||
end
|
||||
updater.__send__(:handle_legacy_taxes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when unused payments records exist which require authorization, " \
|
||||
"but the order is fully paid" do
|
||||
let!(:cash_payment) {
|
||||
build(:payment, state: "completed", amount: order.new_outstanding_balance)
|
||||
}
|
||||
let!(:stripe_payment) { build(:payment, state: "requires_authorization") }
|
||||
before do
|
||||
order.payments << cash_payment
|
||||
order.payments << stripe_payment
|
||||
end
|
||||
|
||||
it "cancels unused payments requiring authorization" do
|
||||
expect(stripe_payment).to receive(:void_transaction!)
|
||||
expect(cash_payment).to_not receive(:void_transaction!)
|
||||
|
||||
order.updater.update_payment_state
|
||||
end
|
||||
def update_order_quantity(order)
|
||||
order.line_items.first.update_attribute(:quantity, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -91,6 +91,18 @@ describe Api::V0::ShipmentsController, type: :controller do
|
||||
|
||||
expect_error_response
|
||||
end
|
||||
|
||||
it "applies any enterprise fees that are present" do
|
||||
order_cycle = create(:simple_order_cycle,
|
||||
coordinator: order.distributor,
|
||||
coordinator_fees: [create(:enterprise_fee, amount: 20)],
|
||||
distributors: [order.distributor],
|
||||
variants: [variant])
|
||||
order.update!(order_cycle_id: order_cycle.id)
|
||||
spree_post :create, params
|
||||
|
||||
expect(order.line_item_adjustments.where(originator_type: "EnterpriseFee")).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
it "can make a shipment ready" do
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
FactoryBot.define do
|
||||
factory :tax_category, class: Spree::TaxCategory do
|
||||
name { "TaxCategory - #{rand(999_999)}" }
|
||||
sequence(:name) { |n| "TaxCategory - #{n}" }
|
||||
description { generate(:random_string) }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
FactoryBot.define do
|
||||
factory :zone, class: Spree::Zone do
|
||||
name { generate(:random_string) }
|
||||
sequence(:name) { |n| "#{generate(:random_string)}#{n}" }
|
||||
description { generate(:random_string) }
|
||||
end
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { Application } from "stimulus";
|
||||
import remote_toggle_controller from "../../../app/webpacker/controllers/remote_toggle_controller";
|
||||
|
||||
describe("RemoteToggleController", () => {
|
||||
beforeAll(() => {
|
||||
const application = Application.start();
|
||||
application.register("remote-toggle", remote_toggle_controller);
|
||||
});
|
||||
|
||||
describe("#toggle", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<div data-controller="remote-toggle" data-remote-toggle-selector-value="#content">
|
||||
<button id="remote-toggle" data-action="click->remote-toggle#toggle"></button>
|
||||
<button id="remote-toggle-with-chevron" data-action="click->remote-toggle#toggle">
|
||||
<i class="icon-chevron-down" data-remote-toggle-target="chevron"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="content">...</div>
|
||||
`;
|
||||
});
|
||||
|
||||
it("clicking a toggle switches the visibility of the :data-remote-toggle-selector element", () => {
|
||||
const button = document.getElementById("remote-toggle");
|
||||
const content = document.getElementById("content");
|
||||
expect(content.style.display).toBe("");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("none");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("block");
|
||||
});
|
||||
|
||||
it("clicking a toggle with a chevron icon switches the visibility of content and the direction of the icon", () => {
|
||||
const button = document.getElementById("remote-toggle-with-chevron");
|
||||
const chevron = button.querySelector("i");
|
||||
const content = document.getElementById("content");
|
||||
expect(content.style.display).toBe("");
|
||||
expect(chevron.className).toBe("icon-chevron-down");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("none");
|
||||
expect(chevron.className).toBe("icon-chevron-up");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("block");
|
||||
expect(chevron.className).toBe("icon-chevron-down");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -108,4 +108,49 @@ describe("ToggleControlController", () => {
|
||||
expect(content.style.display).toBe("block");
|
||||
});
|
||||
});
|
||||
describe("#toggleAdvancedSettings", () => {
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = `
|
||||
<div data-controller="toggle-control" data-toggle-control-selector-value="#content">
|
||||
<button id="remote-toggle" data-action="click->toggle-control#toggleAdvancedSettings"></button>
|
||||
<button id="remote-toggle-with-chevron" data-action="click->toggle-control#toggleAdvancedSettings">
|
||||
<i class="icon-chevron-down" data-toggle-control-target="chevron"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="content">...</div>
|
||||
`;
|
||||
});
|
||||
|
||||
it("clicking a toggle switches the visibility of the :data-remote-toggle-selector element", () => {
|
||||
const button = document.getElementById("remote-toggle");
|
||||
const content = document.getElementById("content");
|
||||
expect(content.style.display).toBe("");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("none");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("block");
|
||||
});
|
||||
|
||||
it("clicking a toggle with a chevron icon switches the visibility of content and the direction of the icon", () => {
|
||||
const button = document.getElementById("remote-toggle-with-chevron");
|
||||
const chevron = button.querySelector("i");
|
||||
const content = document.getElementById("content");
|
||||
expect(content.style.display).toBe("");
|
||||
expect(chevron.className).toBe("icon-chevron-down");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("none");
|
||||
expect(chevron.className).toBe("icon-chevron-up");
|
||||
|
||||
button.click();
|
||||
|
||||
expect(content.style.display).toBe("block");
|
||||
expect(chevron.className).toBe("icon-chevron-down");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -239,8 +239,11 @@ describe SubscriptionConfirmJob do
|
||||
|
||||
context "when payments are processed without error" do
|
||||
before do
|
||||
expect(payment).to receive(:process_offline!) { true }
|
||||
expect(payment).to receive(:completed?) { true }
|
||||
allow(payment).to receive(:process_offline!) do
|
||||
# Mark payment as complete like it would be if sucessfully processed offline
|
||||
payment.state = "complete"
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
it "sends only a subscription confirm email, no regular confirmation emails" do
|
||||
|
||||
Reference in New Issue
Block a user