<commit_before># frozen_string_literal: true

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

module Spree
  class LineItem < ApplicationRecord
    include VariantUnits::VariantAndLineItemNaming
    include LineItemStockChanges

    searchable_attributes :price, :quantity, :order_id, :variant_id, :tax_category_id
    searchable_associations :order, :order_cycle, :variant, :product, :supplier, :tax_category, :option_values
    searchable_scopes :with_tax, :without_tax

    belongs_to :order, class_name: "Spree::Order", inverse_of: :line_items
    has_one :order_cycle, through: :order

    belongs_to :variant, -> { with_deleted }, class_name: "Spree::Variant"
    has_one :product, through: :variant
    has_one :supplier, through: :product
    belongs_to :tax_category, class_name: "Spree::TaxCategory"

    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, :target_shipment # Allows manual skipping of Stock::AvailabilityValidator

    attribute :restock_item, type: :boolean, default: true

    # -- 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(Arel.sql("
          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.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<commit_msg>Copy dimensions from variants to line items before validation<commit_after># frozen_string_literal: true

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

module Spree
  class LineItem < ApplicationRecord
    include VariantUnits::VariantAndLineItemNaming
    include LineItemStockChanges

    searchable_attributes :price, :quantity, :order_id, :variant_id, :tax_category_id
    searchable_associations :order, :order_cycle, :variant, :product, :supplier, :tax_category, :option_values
    searchable_scopes :with_tax, :without_tax

    belongs_to :order, class_name: "Spree::Order", inverse_of: :line_items
    has_one :order_cycle, through: :order

    belongs_to :variant, -> { with_deleted }, class_name: "Spree::Variant"
    has_one :product, through: :variant
    has_one :supplier, through: :product
    belongs_to :tax_category, class_name: "Spree::TaxCategory"

    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
    before_validation :copy_dimensions

    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, :target_shipment # Allows manual skipping of Stock::AvailabilityValidator

    attribute :restock_item, type: :boolean, default: true

    # -- 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(Arel.sql("
          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.currency = variant.currency if currency.nil?
    end

    def copy_tax_category
      return unless variant

      self.tax_category = variant.product.tax_category
    end

    def copy_dimensions
      return unless variant

      self.weight = variant.weight if weight.nil?
      self.height = variant.height if height.nil?
      self.width = variant.width if width.nil?
      self.depth = variant.depth if depth.nil?
    end

    def amount
      price * quantity
    end
{'fraction_non_alphanumeric': 0.08559855026116618, 'fraction_numerical': 0.00021319688732544505, 'mean_word_length': 3.2356659142212187, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5498676', 'n_tokens_mistral': 2819, 'n_tokens_neox': 2743, 'n_words': 653}