mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Merge pull request #13000 from mkllnk/dfc-amend-order
Amend DFC backorder completely
This commit is contained in:
@@ -24,6 +24,7 @@ module Api
|
||||
Orders::WorkflowService.new(@order).advance_to_payment if @order.line_items.any?
|
||||
|
||||
@order.recreate_all_fees!
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
@@ -73,6 +74,7 @@ module Api
|
||||
|
||||
@order.contents.add(variant, quantity, @shipment)
|
||||
@order.recreate_all_fees!
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
@@ -86,6 +88,7 @@ module Api
|
||||
@shipment.reload if @shipment.persisted?
|
||||
|
||||
@order.recreate_all_fees!
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
render json: @shipment, serializer: Api::ShipmentSerializer, status: :ok
|
||||
end
|
||||
|
||||
@@ -70,6 +70,7 @@ module Spree
|
||||
@order.restock_items = params.fetch(:restock_items, "true") == "true"
|
||||
|
||||
if @order.public_send(event.to_s)
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
flash[:success] = Spree.t(:order_updated)
|
||||
else
|
||||
flash[:error] = Spree.t(:cannot_perform_operation)
|
||||
|
||||
@@ -77,6 +77,8 @@ module Spree
|
||||
@order.create_tax_charge!
|
||||
end
|
||||
|
||||
AmendBackorderJob.perform_later(@order) if @order.completed?
|
||||
|
||||
respond_with(@order) do |format|
|
||||
format.html do
|
||||
if params.key?(:checkout)
|
||||
|
||||
@@ -1,98 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# When orders are cancelled, we need to amend
|
||||
# When orders are created, adjusted or cancelled, we need to amend
|
||||
# an existing backorder as well.
|
||||
# We're not dealing with line item changes just yet.
|
||||
class AmendBackorderJob < ApplicationJob
|
||||
sidekiq_options retry: 0
|
||||
|
||||
def self.schedule_bulk_update_for(orders)
|
||||
# We can have one backorder per order cycle and distributor.
|
||||
groups = orders.group_by { |order| [order.order_cycle, order.distributor] }
|
||||
groups.each_value do |orders_with_same_backorder|
|
||||
# We need to trigger only one update per backorder.
|
||||
perform_later(orders_with_same_backorder.first)
|
||||
end
|
||||
end
|
||||
|
||||
def perform(order)
|
||||
OrderLocker.lock_order_and_variants(order) do
|
||||
amend_backorder(order)
|
||||
end
|
||||
end
|
||||
|
||||
# The following is a mix of the BackorderJob and the CompleteBackorderJob.
|
||||
# TODO: Move the common code into a re-usable service class.
|
||||
def amend_backorder(order)
|
||||
order_cycle = order.order_cycle
|
||||
distributor = order.distributor
|
||||
user = distributor.owner
|
||||
items = backorderable_items(order)
|
||||
backorder = BackorderUpdater.new.amend_backorder(order)
|
||||
|
||||
return if items.empty?
|
||||
|
||||
# We are assuming that all variants are linked to the same wholesale
|
||||
# shop and its catalog:
|
||||
reference_link = items[0].variant.semantic_links[0].semantic_id
|
||||
urls = FdcUrlBuilder.new(reference_link)
|
||||
orderer = FdcBackorderer.new(user, urls)
|
||||
|
||||
backorder = orderer.find_open_order(order)
|
||||
|
||||
variants = order_cycle.variants_distributed_by(distributor)
|
||||
adjust_quantities(order_cycle, user, backorder, urls, variants)
|
||||
|
||||
FdcBackorderer.new(user, urls).send_order(backorder)
|
||||
end
|
||||
|
||||
# Check if we have enough stock to reduce the backorder.
|
||||
#
|
||||
# Our local stock can increase when users cancel their orders.
|
||||
# But stock levels could also have been adjusted manually. So we review all
|
||||
# quantities before finalising the order.
|
||||
def adjust_quantities(order_cycle, user, order, urls, variants)
|
||||
broker = FdcOfferBroker.new(user, urls)
|
||||
|
||||
order.lines.each do |line|
|
||||
line.quantity = line.quantity.to_i
|
||||
wholesale_product_id = line.offer.offeredItem.semanticId
|
||||
transformation = broker.wholesale_to_retail(wholesale_product_id)
|
||||
linked_variant = variants.linked_to(transformation.retail_product_id)
|
||||
|
||||
# Assumption: If a transformation is present then we only sell the retail
|
||||
# variant. If that can't be found, it was deleted and we'll ignore that
|
||||
# for now.
|
||||
next if linked_variant.nil?
|
||||
|
||||
# Find all line items for this order cycle
|
||||
# Update quantity accordingly
|
||||
if linked_variant.on_demand
|
||||
release_superfluous_stock(line, linked_variant, transformation)
|
||||
else
|
||||
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up empty lines:
|
||||
order.lines.reject! { |line| line.quantity.zero? }
|
||||
end
|
||||
|
||||
# We look at all linked variants.
|
||||
def backorderable_items(order)
|
||||
order.line_items.select do |item|
|
||||
# TODO: scope variants to hub.
|
||||
# We are only supporting producer stock at the moment.
|
||||
item.variant.semantic_links.present?
|
||||
end
|
||||
end
|
||||
|
||||
def release_superfluous_stock(line, linked_variant, transformation)
|
||||
# Note that a division of integers dismisses the remainder, like `floor`:
|
||||
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
|
||||
|
||||
# But maybe we didn't actually order that much:
|
||||
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
|
||||
line.quantity -= deductable_quantity
|
||||
|
||||
retail_stock_changes = deductable_quantity * transformation.factor
|
||||
linked_variant.on_hand -= retail_stock_changes
|
||||
end
|
||||
|
||||
def aggregate_final_quantities(order_cycle, line, variant, transformation)
|
||||
orders = order_cycle.orders.invoiceable
|
||||
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
|
||||
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
|
||||
line.quantity = wholesale_quantity
|
||||
user = order.distributor.owner
|
||||
urls = nil # Not needed to send order. The backorder id is the URL.
|
||||
FdcBackorderer.new(user, urls).send_order(backorder) if backorder
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,8 +24,7 @@ class CompleteBackorderJob < ApplicationJob
|
||||
|
||||
urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId)
|
||||
|
||||
variants = order_cycle.variants_distributed_by(distributor)
|
||||
adjust_quantities(order_cycle, user, order, urls, variants)
|
||||
BackorderUpdater.new.update(order, user, distributor, order_cycle)
|
||||
|
||||
FdcBackorderer.new(user, urls).complete_order(order)
|
||||
|
||||
@@ -36,55 +35,4 @@ class CompleteBackorderJob < ApplicationJob
|
||||
|
||||
raise
|
||||
end
|
||||
|
||||
# Check if we have enough stock to reduce the backorder.
|
||||
#
|
||||
# Our local stock can increase when users cancel their orders.
|
||||
# But stock levels could also have been adjusted manually. So we review all
|
||||
# quantities before finalising the order.
|
||||
def adjust_quantities(order_cycle, user, order, urls, variants)
|
||||
broker = FdcOfferBroker.new(user, urls)
|
||||
|
||||
order.lines.each do |line|
|
||||
line.quantity = line.quantity.to_i
|
||||
wholesale_product_id = line.offer.offeredItem.semanticId
|
||||
transformation = broker.wholesale_to_retail(wholesale_product_id)
|
||||
linked_variant = variants.linked_to(transformation.retail_product_id)
|
||||
|
||||
# Assumption: If a transformation is present then we only sell the retail
|
||||
# variant. If that can't be found, it was deleted and we'll ignore that
|
||||
# for now.
|
||||
next if linked_variant.nil?
|
||||
|
||||
# Find all line items for this order cycle
|
||||
# Update quantity accordingly
|
||||
if linked_variant.on_demand
|
||||
release_superfluous_stock(line, linked_variant, transformation)
|
||||
else
|
||||
aggregate_final_quantities(order_cycle, line, linked_variant, transformation)
|
||||
end
|
||||
end
|
||||
|
||||
# Clean up empty lines:
|
||||
order.lines.reject! { |line| line.quantity.zero? }
|
||||
end
|
||||
|
||||
def release_superfluous_stock(line, linked_variant, transformation)
|
||||
# Note that a division of integers dismisses the remainder, like `floor`:
|
||||
wholesale_items_contained_in_stock = linked_variant.on_hand / transformation.factor
|
||||
|
||||
# But maybe we didn't actually order that much:
|
||||
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
|
||||
line.quantity -= deductable_quantity
|
||||
|
||||
retail_stock_changes = deductable_quantity * transformation.factor
|
||||
linked_variant.on_hand -= retail_stock_changes
|
||||
end
|
||||
|
||||
def aggregate_final_quantities(order_cycle, line, variant, transformation)
|
||||
orders = order_cycle.orders.invoiceable
|
||||
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
|
||||
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
|
||||
line.quantity = wholesale_quantity
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,8 +142,6 @@ module Spree
|
||||
|
||||
OrderMailer.cancel_email(id).deliver_later if send_cancellation_email
|
||||
update(payment_state: updater.update_payment_state)
|
||||
|
||||
AmendBackorderJob.perform_later(self)
|
||||
end
|
||||
|
||||
def after_resume
|
||||
|
||||
147
app/services/backorder_updater.rb
Normal file
147
app/services/backorder_updater.rb
Normal file
@@ -0,0 +1,147 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'open_food_network/order_cycle_permissions'
|
||||
|
||||
# Update a backorder to reflect all local orders and stock levels
|
||||
# connected to the associated order cycle.
|
||||
class BackorderUpdater
|
||||
# Given an OFN order was created, changed or cancelled,
|
||||
# we re-calculate how much to order in for every variant.
|
||||
def amend_backorder(order)
|
||||
order_cycle = order.order_cycle
|
||||
distributor = order.distributor
|
||||
variants = distributed_linked_variants(order_cycle, distributor)
|
||||
|
||||
# Temporary code: once we don't need a variant link to look up the
|
||||
# backorder, we don't need this check anymore.
|
||||
# Then we can adjust the backorder even though there are no linked variants
|
||||
# in the order cycle right now. Some variants may have been in the order
|
||||
# cycle before and got ordered before being removed from the order cycle.
|
||||
return unless variants.any?
|
||||
|
||||
# We are assuming that all variants are linked to the same wholesale
|
||||
# shop and its catalog:
|
||||
reference_link = variants[0].semantic_links[0].semantic_id
|
||||
user = order.distributor.owner
|
||||
urls = FdcUrlBuilder.new(reference_link)
|
||||
orderer = FdcBackorderer.new(user, urls)
|
||||
|
||||
backorder = orderer.find_open_order(order)
|
||||
|
||||
update(backorder, user, distributor, order_cycle)
|
||||
end
|
||||
|
||||
# Update a given backorder according to a distributor's order cycle.
|
||||
def update(backorder, user, distributor, order_cycle)
|
||||
variants = distributed_linked_variants(order_cycle, distributor)
|
||||
|
||||
# We are assuming that all variants are linked to the same wholesale
|
||||
# shop and its catalog:
|
||||
reference_link = variants[0].semantic_links[0].semantic_id
|
||||
urls = FdcUrlBuilder.new(reference_link)
|
||||
orderer = FdcBackorderer.new(user, urls)
|
||||
broker = FdcOfferBroker.new(user, urls)
|
||||
|
||||
updated_lines = update_order_lines(backorder, order_cycle, variants, broker, orderer)
|
||||
unprocessed_lines = backorder.lines.to_set - updated_lines
|
||||
managed_variants = managed_linked_variants(user, order_cycle, distributor)
|
||||
cancel_stale_lines(unprocessed_lines, managed_variants, broker)
|
||||
|
||||
# Clean up empty lines:
|
||||
backorder.lines.reject! { |line| line.quantity.zero? }
|
||||
|
||||
backorder
|
||||
end
|
||||
|
||||
def update_order_lines(backorder, order_cycle, variants, broker, orderer)
|
||||
variants.map do |variant|
|
||||
link = variant.semantic_links[0].semantic_id
|
||||
solution = broker.best_offer(link)
|
||||
line = orderer.find_or_build_order_line(backorder, solution.offer)
|
||||
if variant.on_demand
|
||||
adjust_stock(variant, solution, line)
|
||||
else
|
||||
aggregate_final_quantities(order_cycle, line, variant, solution)
|
||||
end
|
||||
|
||||
line
|
||||
end
|
||||
end
|
||||
|
||||
def cancel_stale_lines(unprocessed_lines, managed_variants, broker)
|
||||
unprocessed_lines.each do |line|
|
||||
wholesale_quantity = line.quantity.to_i
|
||||
wholesale_product_id = line.offer.offeredItem.semanticId
|
||||
transformation = broker.wholesale_to_retail(wholesale_product_id)
|
||||
linked_variant = managed_variants.linked_to(transformation.retail_product_id)
|
||||
|
||||
if linked_variant.nil?
|
||||
transformation.factor = 1
|
||||
linked_variant = managed_variants.linked_to(wholesale_product_id)
|
||||
end
|
||||
|
||||
# Adjust stock level back, we're not going to order this one.
|
||||
if linked_variant&.on_demand
|
||||
retail_quantity = wholesale_quantity * transformation.factor
|
||||
linked_variant.on_hand -= retail_quantity
|
||||
end
|
||||
|
||||
# We don't have any active orders for this
|
||||
line.quantity = 0
|
||||
end
|
||||
end
|
||||
|
||||
def adjust_stock(variant, solution, line)
|
||||
line.quantity = line.quantity.to_i
|
||||
|
||||
if variant.on_hand.negative?
|
||||
needed_quantity = -1 * variant.on_hand # We need to replenish it.
|
||||
|
||||
# The number of wholesale packs we need to order to fulfill the
|
||||
# needed quantity.
|
||||
# For example, we order 2 packs of 12 cans if we need 15 cans.
|
||||
wholesale_quantity = (needed_quantity.to_f / solution.factor).ceil
|
||||
|
||||
# The number of individual retail items we get with the wholesale order.
|
||||
# For example, if we order 2 packs of 12 cans, we will get 24 cans
|
||||
# and we'll account for that in our stock levels.
|
||||
retail_quantity = wholesale_quantity * solution.factor
|
||||
|
||||
line.quantity += wholesale_quantity
|
||||
variant.on_hand += retail_quantity
|
||||
else
|
||||
# Note that a division of integers dismisses the remainder, like `floor`:
|
||||
wholesale_items_contained_in_stock = variant.on_hand / solution.factor
|
||||
|
||||
# But maybe we didn't actually order that much:
|
||||
deductable_quantity = [line.quantity, wholesale_items_contained_in_stock].min
|
||||
|
||||
if deductable_quantity.positive?
|
||||
line.quantity -= deductable_quantity
|
||||
|
||||
retail_stock_change = deductable_quantity * solution.factor
|
||||
variant.on_hand -= retail_stock_change
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def managed_linked_variants(user, order_cycle, distributor)
|
||||
# These permissions may be too complex. Here may be scope to optimise.
|
||||
permissions = OpenFoodNetwork::OrderCyclePermissions.new(user, order_cycle)
|
||||
permissions.visible_variants_for_outgoing_exchanges_to(distributor)
|
||||
.where.associated(:semantic_links)
|
||||
end
|
||||
|
||||
def distributed_linked_variants(order_cycle, distributor)
|
||||
order_cycle.variants_distributed_by(distributor)
|
||||
.where.associated(:semantic_links)
|
||||
end
|
||||
|
||||
def aggregate_final_quantities(order_cycle, line, variant, transformation)
|
||||
# We may want to query all these quantities in one go instead of this n+1.
|
||||
orders = order_cycle.orders.invoiceable
|
||||
quantity = Spree::LineItem.where(order: orders, variant:).sum(:quantity)
|
||||
wholesale_quantity = (quantity.to_f / transformation.factor).ceil
|
||||
line.quantity = wholesale_quantity
|
||||
end
|
||||
end
|
||||
@@ -152,7 +152,7 @@ class FdcBackorderer
|
||||
end
|
||||
|
||||
def new?(order)
|
||||
order.semanticId == urls.orders_url
|
||||
order.semanticId == urls&.orders_url
|
||||
end
|
||||
|
||||
def build_sale_session(order)
|
||||
|
||||
@@ -15,7 +15,7 @@ module Orders
|
||||
order.send_cancellation_email = @send_cancellation_email
|
||||
order.restock_items = @restock_items
|
||||
order.cancel
|
||||
end
|
||||
end.tap { |orders| AmendBackorderJob.schedule_bulk_update_for(orders) }
|
||||
# rubocop:enable Rails/FindEach
|
||||
end
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ module Orders
|
||||
return unless order.cancel
|
||||
|
||||
Spree::OrderMailer.cancel_email_for_shop(order).deliver_later
|
||||
AmendBackorderJob.perform_later(order)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -50,6 +50,29 @@ RSpec.describe AmendBackorderJob do
|
||||
chia_seed.on_hand = 7
|
||||
end
|
||||
|
||||
describe ".schedule_bulk_update_for" do
|
||||
let(:order_same_oc) {
|
||||
create(
|
||||
:completed_order_with_totals,
|
||||
distributor: order.distributor,
|
||||
order_cycle: order.order_cycle,
|
||||
)
|
||||
}
|
||||
let(:order_other_oc) { create(:completed_order_with_totals) }
|
||||
|
||||
it "enqueues only one job per backorder" do
|
||||
expect {
|
||||
AmendBackorderJob.schedule_bulk_update_for([order, order_same_oc])
|
||||
}.to enqueue_job(AmendBackorderJob).exactly(:once)
|
||||
end
|
||||
|
||||
it "enqueues a job for each backorder" do
|
||||
expect {
|
||||
AmendBackorderJob.schedule_bulk_update_for([order, order_other_oc])
|
||||
}.to enqueue_job(AmendBackorderJob).exactly(:twice)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#amend_backorder" do
|
||||
it "updates an order" do
|
||||
stub_request(:get, catalog_url).to_return(body: catalog_json)
|
||||
@@ -86,13 +109,26 @@ RSpec.describe AmendBackorderJob do
|
||||
expect(backorder.lines[0].quantity).to eq 1 # beans
|
||||
expect(backorder.lines[1].quantity).to eq 5 # chia
|
||||
|
||||
# We cancel the only order and that should reduce the order lines to 0.
|
||||
expect { order.cancel! }
|
||||
.to change { beans.reload.on_hand }.from(9).to(15)
|
||||
.and change { chia_seed.reload.on_hand }.from(7).to(12)
|
||||
# We increase quantities which should be reflected in the backorder:
|
||||
beans.on_hand = -1
|
||||
chia_item.quantity += 3
|
||||
chia_item.save!
|
||||
|
||||
expect { subject.amend_backorder(order) }
|
||||
.to change { backorder.lines.count }.from(2).to(0)
|
||||
.to change { beans.on_hand }.from(-1).to(11)
|
||||
.and change { backorder.lines[0].quantity }.from(1).to(2)
|
||||
.and change { backorder.lines[1].quantity }.from(5).to(8)
|
||||
|
||||
# We cancel the only order.
|
||||
expect { order.cancel! }
|
||||
.to change { beans.reload.on_hand }.from(11).to(17)
|
||||
.and change { chia_seed.reload.on_hand }.from(4).to(12)
|
||||
|
||||
# But we decreased the stock of beans outside of orders above.
|
||||
# So only the chia seeds are cancelled. The beans still need replenishing.
|
||||
expect { subject.amend_backorder(order) }
|
||||
.to change { backorder.lines.count }.from(2).to(1)
|
||||
.and change { beans.reload.on_hand }.by(-12)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
120
spec/services/backorder_updater_spec.rb
Normal file
120
spec/services/backorder_updater_spec.rb
Normal file
@@ -0,0 +1,120 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BackorderUpdater do
|
||||
let(:order) { create(:completed_order_with_totals) }
|
||||
let(:distributor) { order.distributor }
|
||||
let(:beans) { beans_item.variant }
|
||||
let(:beans_item) { order.line_items[0] }
|
||||
let(:chia_seed) { chia_item.variant }
|
||||
let(:chia_item) { order.line_items[1] }
|
||||
let(:user) { order.distributor.owner }
|
||||
let(:catalog_json) { file_fixture("fdc-catalog.json").read }
|
||||
let(:catalog_url) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts"
|
||||
}
|
||||
let(:product_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
|
||||
}
|
||||
let(:chia_seed_wholesale_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519468433715"
|
||||
}
|
||||
|
||||
before do
|
||||
# This ensures that callbacks adjust stock correctly.
|
||||
# See: https://github.com/openfoodfoundation/openfoodnetwork/pull/12938
|
||||
order.reload
|
||||
|
||||
user.oidc_account = build(:testdfc_account)
|
||||
|
||||
beans.semantic_links << SemanticLink.new(
|
||||
semantic_id: product_link
|
||||
)
|
||||
chia_seed.semantic_links << SemanticLink.new(
|
||||
semantic_id: chia_seed_wholesale_link
|
||||
)
|
||||
order.order_cycle = create(
|
||||
:simple_order_cycle,
|
||||
distributors: [distributor],
|
||||
variants: order.variants,
|
||||
)
|
||||
order.save!
|
||||
|
||||
beans.on_demand = true
|
||||
beans_item.update!(quantity: 6)
|
||||
beans.on_hand = -3
|
||||
|
||||
chia_item.update!(quantity: 5)
|
||||
chia_seed.on_demand = false
|
||||
chia_seed.on_hand = 7
|
||||
end
|
||||
|
||||
describe "#amend_backorder" do
|
||||
it "updates an order" do
|
||||
stub_request(:get, catalog_url).to_return(body: catalog_json)
|
||||
|
||||
# Record the placed backorder:
|
||||
backorder = nil
|
||||
allow_any_instance_of(FdcBackorderer).to receive(:find_order) do |*_args|
|
||||
backorder
|
||||
end
|
||||
allow_any_instance_of(FdcBackorderer).to receive(:find_open_order) do |*_args|
|
||||
backorder
|
||||
end
|
||||
allow_any_instance_of(FdcBackorderer).to receive(:send_order) do |*args|
|
||||
backorder = args[1]
|
||||
end
|
||||
|
||||
BackorderJob.new.place_backorder(order)
|
||||
|
||||
# We ordered a case of 12 cans: -3 + 12 = 9
|
||||
expect(beans.on_hand).to eq 9
|
||||
|
||||
# Stock controlled items don't change stock in backorder:
|
||||
expect(chia_seed.on_hand).to eq 7
|
||||
|
||||
expect(backorder.lines[0].quantity).to eq 1 # beans
|
||||
expect(backorder.lines[1].quantity).to eq 5 # chia
|
||||
|
||||
# Without any change, the backorder shouldn't get changed either:
|
||||
subject.amend_backorder(order)
|
||||
|
||||
# Same as before:
|
||||
expect(beans.on_hand).to eq 9
|
||||
expect(chia_seed.on_hand).to eq 7
|
||||
expect(backorder.lines[0].quantity).to eq 1 # beans
|
||||
expect(backorder.lines[1].quantity).to eq 5 # chia
|
||||
|
||||
# We increase quantities which should be reflected in the backorder:
|
||||
beans.on_hand = -1
|
||||
chia_item.quantity += 3
|
||||
chia_item.save!
|
||||
|
||||
expect { subject.amend_backorder(order) }
|
||||
.to change { beans.on_hand }.from(-1).to(11)
|
||||
.and change { backorder.lines[0].quantity }.from(1).to(2)
|
||||
.and change { backorder.lines[1].quantity }.from(5).to(8)
|
||||
|
||||
# We cancel the only order.
|
||||
expect { order.cancel! }
|
||||
.to change { beans.reload.on_hand }.from(11).to(17)
|
||||
.and change { chia_seed.reload.on_hand }.from(4).to(12)
|
||||
|
||||
# But we decreased the stock of beans outside of orders above.
|
||||
# So only the chia seeds are cancelled. The beans still need replenishing.
|
||||
expect { subject.amend_backorder(order) }
|
||||
.to change { backorder.lines.count }.from(2).to(1)
|
||||
.and change { beans.reload.on_hand }.by(-12)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#distributed_linked_variants" do
|
||||
let(:order_cycle) { order.order_cycle }
|
||||
|
||||
it "selects available variants with semantic links" do
|
||||
variants = subject.distributed_linked_variants(order_cycle, distributor)
|
||||
expect(variants).to match_array [beans, chia_seed]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -106,4 +106,20 @@ RSpec.describe FdcBackorderer do
|
||||
expect(found_line).to eq existing_line
|
||||
end
|
||||
end
|
||||
|
||||
describe "#new?" do
|
||||
describe "without knowing URLs" do
|
||||
let(:subject) { FdcBackorderer.new(nil, nil) }
|
||||
|
||||
it "recognises new orders" do
|
||||
order = DataFoodConsortium::Connector::Order.new(nil)
|
||||
expect(subject.new?(order)).to eq true
|
||||
end
|
||||
|
||||
it "recognises existing orders" do
|
||||
order = DataFoodConsortium::Connector::Order.new("https://order")
|
||||
expect(subject.new?(order)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -447,6 +447,7 @@ RSpec.describe '
|
||||
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
|
||||
@@ -462,22 +463,25 @@ RSpec.describe '
|
||||
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)
|
||||
click_on "Cancel" # Cancels the cancel action
|
||||
}.not_to enqueue_mail
|
||||
end
|
||||
|
||||
expect(page).not_to have_content "This will cancel the current order."
|
||||
|
||||
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)
|
||||
expect {
|
||||
within ".reveal-modal" do
|
||||
click_on "Confirm" # Confirms the cancel action
|
||||
end
|
||||
expect(page).to have_content("CANCELLED", count: 2)
|
||||
}.to enqueue_job(AmendBackorderJob).exactly(:twice)
|
||||
# You can't combine negative matchers.
|
||||
.and enqueue_mail.exactly(0).times
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user