Merge Spree::Stock::Package into OrderManagement::Stock::Package

This commit is contained in:
Luis Ramos
2020-07-02 14:47:01 +01:00
parent 943cb7bf05
commit f0b3ed0d79
7 changed files with 262 additions and 300 deletions

View File

@@ -41,4 +41,8 @@ Spree::AppConfiguration.class_eval do
# Enable cache
preference :enable_products_cache?, :boolean, default: (Rails.env.production? || Rails.env.staging?)
def package_factory
@package_factory ||= OrderManagement::Stock::Package
end
end

View File

@@ -1,118 +0,0 @@
# frozen_string_literal: true
module Spree
module Stock
class Package
ContentItem = Struct.new(:variant, :quantity, :state)
attr_reader :stock_location, :order, :contents
attr_accessor :shipping_rates
def initialize(stock_location, order, contents = [])
@stock_location = stock_location
@order = order
@contents = contents
@shipping_rates = []
end
def add(variant, quantity, state = :on_hand)
contents << ContentItem.new(variant, quantity, state)
end
def weight
contents.sum { |item| item.variant.weight * item.quantity }
end
def on_hand
contents.select { |item| item.state == :on_hand }
end
def backordered
contents.select { |item| item.state == :backordered }
end
def find_item(variant, state = :on_hand)
contents.select do |item|
item.variant == variant &&
item.state == state
end.first
end
def quantity(state = nil)
case state
when :on_hand
on_hand.sum(&:quantity)
when :backordered
backordered.sum(&:quantity)
else
contents.sum(&:quantity)
end
end
def empty?
quantity.zero?
end
def flattened
flat = []
contents.each do |item|
item.quantity.times do
flat << ContentItem.new(item.variant, 1, item.state)
end
end
flat
end
def flattened=(flattened)
contents.clear
flattened.each do |item|
current_item = find_item(item.variant, item.state)
if current_item
current_item.quantity += 1
else
add(item.variant, item.quantity, item.state)
end
end
end
def currency
# TODO calculate from first variant?
end
def shipping_categories
contents.map { |item| item.variant.shipping_category }.compact.uniq
end
def shipping_methods
shipping_categories.map(&:shipping_methods).flatten.uniq
end
def inspect
out = "#{order} - "
out << contents.map do |content_item|
"#{content_item.variant.name} #{content_item.quantity} #{content_item.state}"
end.join('/')
out
end
def to_shipment
shipment = Spree::Shipment.new
shipment.order = order
shipment.stock_location = stock_location
shipment.shipping_rates = shipping_rates
contents.each do |item|
item.quantity.times do
unit = shipment.inventory_units.build
unit.pending = true
unit.order = order
unit.variant = item.variant
unit.state = item.state.to_s
end
end
shipment
end
end
end
end

View File

@@ -30,7 +30,6 @@ Spree.config do |config|
config.auto_capture = true
#config.override_actionmailer_config = false
config.package_factory = OrderManagement::Stock::Package
config.order_updater_decorator = OrderUpdater
# S3 settings

View File

@@ -1,22 +1,87 @@
# frozen_string_literal: true
# Extends Spree's Package implementation to skip shipping methods that are not
# valid for OFN.
#
# It requires the following configuration in config/initializers/spree.rb:
#
# Spree.config do |config|
# ...
# config.package_factory = OrderManagement::Stock::Package
# end
#
module OrderManagement
module Stock
class Package < Spree::Stock::Package
class Package
ContentItem = Struct.new(:variant, :quantity, :state)
attr_reader :stock_location, :order, :contents
attr_accessor :shipping_rates
def initialize(stock_location, order, contents = [])
@stock_location = stock_location
@order = order
@contents = contents
@shipping_rates = []
end
def add(variant, quantity, state = :on_hand)
contents << ContentItem.new(variant, quantity, state)
end
def weight
contents.sum { |item| item.variant.weight * item.quantity }
end
def on_hand
contents.select { |item| item.state == :on_hand }
end
def backordered
contents.select { |item| item.state == :backordered }
end
def find_item(variant, state = :on_hand)
contents.select do |item|
item.variant == variant &&
item.state == state
end.first
end
def quantity(state = nil)
case state
when :on_hand
on_hand.sum(&:quantity)
when :backordered
backordered.sum(&:quantity)
else
contents.sum(&:quantity)
end
end
def empty?
quantity.zero?
end
def flattened
flat = []
contents.each do |item|
item.quantity.times do
flat << ContentItem.new(item.variant, 1, item.state)
end
end
flat
end
def flattened=(flattened)
contents.clear
flattened.each do |item|
current_item = find_item(item.variant, item.state)
if current_item
current_item.quantity += 1
else
add(item.variant, item.quantity, item.state)
end
end
end
def currency
# TODO calculate from first variant?
end
# Returns all existing shipping categories.
# It does not filter by the shipping categories of the products in the order: it allows
# checkout of products with categories that are not the shipping method's categories
# It disables the matching of product shipping category with shipping method's category
# It allows checkout of products with categories that are not the ship method's categories
#
# @return [Array<Spree::ShippingCategory>]
def shipping_categories
@@ -27,13 +92,40 @@ module OrderManagement
#
# @return [Array<Spree::ShippingMethod>]
def shipping_methods
available_shipping_methods = super.to_a
available_shipping_methods = shipping_categories.map(&:shipping_methods).flatten.uniq.to_a
available_shipping_methods.keep_if do |shipping_method|
ships_with?(order.distributor.shipping_methods.to_a, shipping_method)
end
end
def inspect
out = "#{order} - "
out << contents.map do |content_item|
"#{content_item.variant.name} #{content_item.quantity} #{content_item.state}"
end.join('/')
out
end
def to_shipment
shipment = Spree::Shipment.new
shipment.order = order
shipment.stock_location = stock_location
shipment.shipping_rates = shipping_rates
contents.each do |item|
item.quantity.times do
unit = shipment.inventory_units.build
unit.pending = true
unit.order = order
unit.variant = item.variant
unit.state = item.state.to_s
end
end
shipment
end
private
# Checks whether the given distributor provides the specified shipping method

View File

@@ -1,58 +1,171 @@
# frozen_string_literal: true
require 'spec_helper'
module OrderManagement
module Stock
describe Package do
let(:stock_location) { double(:stock_location) }
context "base tests" do
let(:variant) { build(:variant, weight: 25.0) }
let(:stock_location) { build(:stock_location) }
let(:distributor) { create(:enterprise) }
let(:order) { build(:order, distributor: distributor) }
subject(:package) { Package.new(stock_location, order, contents) }
subject { Package.new(stock_location, order) }
let(:enterprise) { create(:enterprise) }
let(:other_enterprise) { create(:enterprise) }
it 'calculates the weight of all the contents' do
subject.add variant, 4
expect(subject.weight).to eq 100.0
end
let(:order) { build(:order, distributor: enterprise) }
it 'filters by on_hand and backordered' do
subject.add variant, 4, :on_hand
subject.add variant, 3, :backordered
expect(subject.on_hand.count).to eq 1
expect(subject.backordered.count).to eq 1
end
let(:variant1) do
instance_double(
Spree::Variant,
shipping_category: shipping_method1.shipping_categories.first
)
end
let(:variant2) do
instance_double(
Spree::Variant,
shipping_category: shipping_method2.shipping_categories.first
)
end
let(:variant3) do
instance_double(Spree::Variant, shipping_category: nil)
end
it 'calculates the quantity by state' do
subject.add variant, 4, :on_hand
subject.add variant, 3, :backordered
let(:contents) do
[
Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant2, 1),
Package::ContentItem.new(variant3, 1)
]
end
expect(subject.quantity).to eq 7
expect(subject.quantity(:on_hand)).to eq 4
expect(subject.quantity(:backordered)).to eq 3
end
let(:shipping_method1) { create(:shipping_method, distributors: [enterprise]) }
let(:shipping_method2) { create(:shipping_method, distributors: [other_enterprise]) }
it 'returns nil for content item not found' do
item = subject.find_item(variant, :on_hand)
expect(item).to be_nil
end
describe '#shipping_methods' do
it 'does not return shipping methods not used by the package\'s order distributor' do
expect(package.shipping_methods).to eq [shipping_method1]
it 'finds content item for a variant' do
subject.add variant, 4, :on_hand
item = subject.find_item(variant, :on_hand)
expect(item.quantity).to eq 4
end
it 'get flattened contents' do
subject.add variant, 4, :on_hand
subject.add variant, 2, :backordered
flattened = subject.flattened
expect(flattened.select { |i| i.state == :on_hand }.size).to eq 4
expect(flattened.select { |i| i.state == :backordered }.size).to eq 2
end
it 'set contents from flattened' do
flattened = [Package::ContentItem.new(variant, 1, :on_hand),
Package::ContentItem.new(variant, 1, :on_hand),
Package::ContentItem.new(variant, 1, :backordered),
Package::ContentItem.new(variant, 1, :backordered)]
subject.flattened = flattened
expect(subject.on_hand.size).to eq 1
expect(subject.on_hand.first.quantity).to eq 2
expect(subject.backordered.size).to eq 1
end
# Contains regression test for #2804
it 'builds a list of shipping methods from all categories' do
shipping_method1 = create(:shipping_method, distributors: [distributor])
shipping_method2 = create(:shipping_method, distributors: [distributor])
variant1 = create(:variant,
shipping_category: shipping_method1.shipping_categories.first)
variant2 = create(:variant,
shipping_category: shipping_method2.shipping_categories.first)
variant3 = create(:variant, shipping_category: nil)
contents = [Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant2, 1),
Package::ContentItem.new(variant3, 1)]
package = Package.new(stock_location, order, contents)
expect(package.shipping_methods.size).to eq 2
end
it "can convert to a shipment" do
flattened = [Package::ContentItem.new(variant, 2, :on_hand),
Package::ContentItem.new(variant, 1, :backordered)]
subject.flattened = flattened
shipping_method = build(:shipping_method)
subject.shipping_rates = [
Spree::ShippingRate.new(shipping_method: shipping_method, cost: 10.00, selected: true)
]
shipment = subject.to_shipment
expect(shipment.order).to eq subject.order
expect(shipment.stock_location).to eq subject.stock_location
expect(shipment.inventory_units.size).to eq 3
first_unit = shipment.inventory_units.first
expect(first_unit.variant).to eq variant
expect(first_unit.state).to eq 'on_hand'
expect(first_unit.order).to eq subject.order
expect(first_unit).to be_pending
last_unit = shipment.inventory_units.last
expect(last_unit.variant).to eq variant
expect(last_unit.state).to eq 'backordered'
expect(last_unit.order).to eq subject.order
expect(shipment.shipping_method).to eq shipping_method
end
end
describe '#shipping_categories' do
it "returns shipping categories that are not shipping categories of the order's products" do
package
other_shipping_category = Spree::ShippingCategory.create(name: "Custom")
context "#shipping_methods and #shipping_categories" do
let(:stock_location) { double(:stock_location) }
expect(package.shipping_categories).to eq [shipping_method1.shipping_categories.first,
other_shipping_category]
subject(:package) { Package.new(stock_location, order, contents) }
let(:enterprise) { create(:enterprise) }
let(:other_enterprise) { create(:enterprise) }
let(:order) { build(:order, distributor: enterprise) }
let(:variant1) do
instance_double(
Spree::Variant,
shipping_category: shipping_method1.shipping_categories.first
)
end
let(:variant2) do
instance_double(
Spree::Variant,
shipping_category: shipping_method2.shipping_categories.first
)
end
let(:variant3) do
instance_double(Spree::Variant, shipping_category: nil)
end
let(:contents) do
[
Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant2, 1),
Package::ContentItem.new(variant3, 1)
]
end
let(:shipping_method1) { create(:shipping_method, distributors: [enterprise]) }
let(:shipping_method2) { create(:shipping_method, distributors: [other_enterprise]) }
describe '#shipping_methods' do
it 'does not return shipping methods not used by the package\'s order distributor' do
expect(package.shipping_methods).to eq [shipping_method1]
end
end
describe '#shipping_categories' do
it "returns ship categories that are not the ship categories of the order's products" do
package
other_shipping_category = Spree::ShippingCategory.create(name: "Custom")
expect(package.shipping_categories).to eq [shipping_method1.shipping_categories.first,
other_shipping_category]
end
end
end
end

View File

@@ -1,115 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
module Spree
module Stock
describe Package do
let(:variant) { build(:variant, weight: 25.0) }
let(:stock_location) { build(:stock_location) }
let(:order) { build(:order) }
subject { Package.new(stock_location, order) }
it 'calculates the weight of all the contents' do
subject.add variant, 4
expect(subject.weight).to eq 100.0
end
it 'filters by on_hand and backordered' do
subject.add variant, 4, :on_hand
subject.add variant, 3, :backordered
expect(subject.on_hand.count).to eq 1
expect(subject.backordered.count).to eq 1
end
it 'calculates the quantity by state' do
subject.add variant, 4, :on_hand
subject.add variant, 3, :backordered
expect(subject.quantity).to eq 7
expect(subject.quantity(:on_hand)).to eq 4
expect(subject.quantity(:backordered)).to eq 3
end
it 'returns nil for content item not found' do
item = subject.find_item(variant, :on_hand)
expect(item).to be_nil
end
it 'finds content item for a variant' do
subject.add variant, 4, :on_hand
item = subject.find_item(variant, :on_hand)
expect(item.quantity).to eq 4
end
it 'get flattened contents' do
subject.add variant, 4, :on_hand
subject.add variant, 2, :backordered
flattened = subject.flattened
expect(flattened.select { |i| i.state == :on_hand }.size).to eq 4
expect(flattened.select { |i| i.state == :backordered }.size).to eq 2
end
it 'set contents from flattened' do
flattened = [Package::ContentItem.new(variant, 1, :on_hand),
Package::ContentItem.new(variant, 1, :on_hand),
Package::ContentItem.new(variant, 1, :backordered),
Package::ContentItem.new(variant, 1, :backordered)]
subject.flattened = flattened
expect(subject.on_hand.size).to eq 1
expect(subject.on_hand.first.quantity).to eq 2
expect(subject.backordered.size).to eq 1
end
# Contains regression test for #2804
it 'builds a list of shipping methods from all categories' do
shipping_method1 = create(:shipping_method)
shipping_method2 = create(:shipping_method)
variant1 = create(:variant,
shipping_category: shipping_method1.shipping_categories.first)
variant2 = create(:variant,
shipping_category: shipping_method2.shipping_categories.first)
variant3 = create(:variant, shipping_category: nil)
contents = [Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant1, 1),
Package::ContentItem.new(variant2, 1),
Package::ContentItem.new(variant3, 1)]
package = Package.new(stock_location, order, contents)
expect(package.shipping_methods.size).to eq 2
end
it "can convert to a shipment" do
flattened = [Package::ContentItem.new(variant, 2, :on_hand),
Package::ContentItem.new(variant, 1, :backordered)]
subject.flattened = flattened
shipping_method = build(:shipping_method)
subject.shipping_rates = [
Spree::ShippingRate.new(shipping_method: shipping_method, cost: 10.00, selected: true)
]
shipment = subject.to_shipment
expect(shipment.order).to eq subject.order
expect(shipment.stock_location).to eq subject.stock_location
expect(shipment.inventory_units.size).to eq 3
first_unit = shipment.inventory_units.first
expect(first_unit.variant).to eq variant
expect(first_unit.state).to eq 'on_hand'
expect(first_unit.order).to eq subject.order
expect(first_unit).to be_pending
last_unit = shipment.inventory_units.last
expect(last_unit.variant).to eq variant
expect(last_unit.state).to eq 'backordered'
expect(last_unit.order).to eq subject.order
expect(shipment.shipping_method).to eq shipping_method
end
end
end
end

View File

@@ -39,19 +39,6 @@ module Spree
expect(package.backordered.size).to eq 5
end
context 'when a packer factory is not specified' do
let(:package) { double(:package, add: true) }
it 'calls Spree::Stock::Package' do
expect(Package)
.to receive(:new)
.with(stock_location, order)
.and_return(package)
subject.default_package
end
end
context 'when a packer factory is specified' do
before do
allow(Spree::Config).to receive(:package_factory) { TestPackageFactory }