Skip to content

Commit

Permalink
Merge pull request #5887 from luisramos0/orders
Browse files Browse the repository at this point in the history
[Bye bye Spree] Bring models order, line_item and other related from spree_core
  • Loading branch information
luisramos0 authored Oct 15, 2020
2 parents f1e2040 + a4c8380 commit d54ddac
Show file tree
Hide file tree
Showing 25 changed files with 3,259 additions and 637 deletions.
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

0 comments on commit d54ddac

Please sign in to comment.