Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bye bye Spree] Bring models order, line_item and other related from spree_core #5887

Merged
merged 21 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
47d2f69
Bring models related to Order from spree_core
luisramos0 Aug 8, 2020
6900f7a
Merge decorators with original files from spree_core
luisramos0 Aug 8, 2020
2a6d83b
Remove confirm checkout step and it's additional removal
luisramos0 Aug 8, 2020
cc87e8c
Remove code related to promotions
luisramos0 Aug 8, 2020
82a116a
We always define Spree.user_class
luisramos0 Aug 8, 2020
3c5a35d
Remove original email validator and keep only previous OFN validator
luisramos0 Aug 8, 2020
94ad02a
Run rubocop autocorrect
luisramos0 Aug 8, 2020
2cd0662
Fix easy rubocop issues
luisramos0 Aug 8, 2020
2753e86
Run rubocop autocorrect
luisramos0 Aug 8, 2020
8643cbd
Delete unused order.merge! and fix specs
luisramos0 Aug 8, 2020
31f9cd3
Fix spec/models/spree/order specs
luisramos0 Aug 8, 2020
4215dcb
Run transpec on the new specs from spree_core
luisramos0 Aug 8, 2020
7884dbf
Revert rubocop autocorrect, each is needed here for find_each is not …
luisramos0 Aug 8, 2020
c49dbec
Adapt order_spec to new updater code
luisramos0 Aug 21, 2020
03419bb
Remove all and use find_each instead!
luisramos0 Sep 1, 2020
a5ff4d6
Remove unneeded setup code and remove unnecessary reference to Factor…
luisramos0 Sep 2, 2020
e0d731b
Remove unused email validator
luisramos0 Sep 5, 2020
f81d459
Use correct updater
luisramos0 Sep 3, 2020
fe7cf0c
Remove reference to FactoryGirl
luisramos0 Sep 3, 2020
4b597ad
Fix easy rubocop issues
luisramos0 Sep 5, 2020
a4c8380
Remove removal of transition to confirm as confirm does not exist any…
luisramos0 Oct 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions app/models/spree/inventory_unit.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

module Spree
class InventoryUnit < ActiveRecord::Base
belongs_to :variant, class_name: "Spree::Variant"
belongs_to :order, class_name: "Spree::Order"
belongs_to :shipment, class_name: "Spree::Shipment"
belongs_to :return_authorization, class_name: "Spree::ReturnAuthorization"

scope :backordered, -> { where state: 'backordered' }
scope :shipped, -> { where state: 'shipped' }
scope :backordered_per_variant, ->(stock_item) do
includes(:shipment)
.where("spree_shipments.state != 'canceled'").references(:shipment)
.where(variant_id: stock_item.variant_id)
.backordered.order("#{table_name}.created_at ASC")
end

# state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
state_machine initial: :on_hand do
event :fill_backorder do
transition to: :on_hand, from: :backordered
end
after_transition on: :fill_backorder, do: :update_order

event :ship do
transition to: :shipped, if: :allow_ship?
end

event :return do
transition to: :returned, from: :shipped
end
end

# This was refactored from a simpler query because the previous implementation
# lead to issues once users tried to modify the objects returned. That's due
# to ActiveRecord `joins(shipment: :stock_location)` only return readonly
# objects
#
# Returns an array of backordered inventory units as per a given stock item
def self.backordered_for_stock_item(stock_item)
backordered_per_variant(stock_item).select do |unit|
unit.shipment.stock_location == stock_item.stock_location
end
end

def self.finalize_units!(inventory_units)
inventory_units.map { |iu| iu.update_column(:pending, false) }
end

def find_stock_item
Spree::StockItem.find_by(stock_location_id: shipment.stock_location_id,
variant_id: variant_id)
end

# Remove variant default_scope `deleted_at: nil`
def variant
Spree::Variant.unscoped { super }
end

private

def allow_ship?
Spree::Config[:allow_backorder_shipping] || on_hand?
end

def update_order
order.update!
end
end
end
264 changes: 264 additions & 0 deletions app/models/spree/line_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
# frozen_string_literal: true

require 'open_food_network/scope_variant_to_hub'
require 'variant_units/variant_and_line_item_naming'

module Spree
class LineItem < ActiveRecord::Base
include VariantUnits::VariantAndLineItemNaming
include LineItemBasedAdjustmentHandling

belongs_to :order, class_name: "Spree::Order", inverse_of: :line_items
belongs_to :variant, class_name: "Spree::Variant"
belongs_to :tax_category, class_name: "Spree::TaxCategory"

has_one :product, through: :variant
has_many :adjustments, as: :adjustable, dependent: :destroy

has_and_belongs_to_many :option_values, join_table: 'spree_option_values_line_items',
class_name: 'Spree::OptionValue'

before_validation :adjust_quantity
before_validation :copy_price
before_validation :copy_tax_category

validates :variant, presence: true
validates :quantity, numericality: {
only_integer: true,
greater_than: -1,
message: Spree.t('validation.must_be_int')
}
validates :price, numericality: true
validates_with Stock::AvailabilityValidator

before_save :update_inventory
before_save :calculate_final_weight_volume, if: :quantity_changed?,
unless: :final_weight_volume_changed?
after_save :update_order
after_save :update_units
before_destroy :update_inventory_before_destroy
after_destroy :update_order

delegate :product, :unit_description, :display_name, to: :variant

attr_accessor :skip_stock_check # Allows manual skipping of Stock::AvailabilityValidator
attr_accessor :target_shipment

# -- Scopes
scope :managed_by, lambda { |user|
if user.has_spree_role?('admin')
where(nil)
else
# Find line items that are from orders distributed by the user or supplied by the user
joins(variant: :product).
joins(:order).
where('spree_orders.distributor_id IN (?) OR spree_products.supplier_id IN (?)',
user.enterprises, user.enterprises).
select('spree_line_items.*')
end
}

scope :in_orders, lambda { |orders|
where(order_id: orders)
}

# Find line items that are from order sorted by variant name and unit value
scope :sorted_by_name_and_unit_value, -> {
joins(variant: :product).
reorder("
lower(spree_products.name) asc,
lower(spree_variants.display_name) asc,
spree_variants.unit_value asc")
}

scope :from_order_cycle, lambda { |order_cycle|
joins(order: :order_cycle).
where('order_cycles.id = ?', order_cycle)
}

# Here we are simply joining the line item to its variant and product
# We dont use joins here to avoid the default scopes,
# and with that, include deleted variants and deleted products
scope :supplied_by_any, lambda { |enterprises|
product_ids = Spree::Product.unscoped.where(supplier_id: enterprises).select(:id)
variant_ids = Spree::Variant.unscoped.where(product_id: product_ids).select(:id)
where("spree_line_items.variant_id IN (?)", variant_ids)
}

scope :with_tax, -> {
joins(:adjustments).
where('spree_adjustments.originator_type = ?', 'Spree::TaxRate').
select('DISTINCT spree_line_items.*')
}

# Line items without a Spree::TaxRate-originated adjustment
scope :without_tax, -> {
joins("
LEFT OUTER JOIN spree_adjustments
ON (spree_adjustments.adjustable_id=spree_line_items.id
AND spree_adjustments.adjustable_type = 'Spree::LineItem'
AND spree_adjustments.originator_type='Spree::TaxRate')").
where('spree_adjustments.id IS NULL')
}

def copy_price
return unless variant

self.price = variant.price if price.nil?
self.cost_price = variant.cost_price if cost_price.nil?
self.currency = variant.currency if currency.nil?
end

def copy_tax_category
return unless variant

self.tax_category = variant.product.tax_category
end

def amount
price * quantity
end
alias total amount

def single_money
Spree::Money.new(price, currency: currency)
end
alias single_display_amount single_money

def money
Spree::Money.new(amount, currency: currency)
end
alias display_total money
alias display_amount money

def adjust_quantity
self.quantity = 0 if quantity.nil? || quantity < 0
end

# Here we skip stock check if skip_stock_check flag is active,
# we skip stock check if requested quantity is zero or negative,
# and we scope variants to hub and thus acivate variant overrides.
def sufficient_stock?
return true if skip_stock_check
return true if quantity <= 0

scoper.scope(variant)
variant.can_supply?(quantity)
end

def insufficient_stock?
!sufficient_stock?
end

def assign_stock_changes_to=(shipment)
@preferred_shipment = shipment
end

# Remove product default_scope `deleted_at: nil`
def product
variant.product
end

# This ensures that LineItems always have access to soft-deleted variants.
# In some situations, unscoped super will be nil. In these cases,
# we fetch the variant using variant_id. See issue #4946 for more details.
def variant
Spree::Variant.unscoped { super } || Spree::Variant.unscoped.find(variant_id)
end

def cap_quantity_at_stock!
scoper.scope(variant)
return if variant.on_demand

update!(quantity: variant.on_hand) if quantity > variant.on_hand
end

def has_tax?
adjustments.included_tax.any?
end

def included_tax
adjustments.included_tax.sum(&:included_tax)
end

def tax_rates
product.tax_category.andand.tax_rates || []
end

def price_with_adjustments
# EnterpriseFee#create_adjustment applies adjustments on line items to their parent order,
# so line_item.adjustments returns an empty array
return 0 if quantity.zero?

line_item_adjustments = OrderAdjustmentsFetcher.new(order).line_item_adjustments(self)

(price + line_item_adjustments.sum(&:amount) / quantity).round(2)
end

def single_display_amount_with_adjustments
Spree::Money.new(price_with_adjustments, currency: currency)
end

def amount_with_adjustments
# We calculate from price_with_adjustments here rather than building our own value because
# rounding errors can produce discrepencies of $0.01.
price_with_adjustments * quantity
end

def display_amount_with_adjustments
Spree::Money.new(amount_with_adjustments, currency: currency)
end

def display_included_tax
Spree::Money.new(included_tax, currency: currency)
end

def unit_value
return variant.unit_value if quantity == 0 || !final_weight_volume

final_weight_volume / quantity
end

def scoper
@scoper ||= OpenFoodNetwork::ScopeVariantToHub.new(order.distributor)
end

private

def update_inventory
return unless changed?

scoper.scope(variant)
Spree::OrderInventory.new(order).verify(self, target_shipment)
end

def update_order
return unless changed? || destroyed?

# update the order totals, etc.
order.create_tax_charge!
order.update!
end

def update_inventory_before_destroy
# This is necessary before destroying the line item
# so that update_inventory will restore stock to the variant
self.quantity = 0

update_inventory

# This is necessary after updating inventory
# because update_inventory may delete the last shipment in the order
# and that makes update_order fail if we don't reload the shipments
order.shipments.reload
end

def calculate_final_weight_volume
if final_weight_volume.present? && quantity_was > 0
self.final_weight_volume = final_weight_volume * quantity / quantity_was
elsif variant.andand.unit_value.present?
self.final_weight_volume = variant.andand.unit_value * quantity
end
end
end
end
Loading