# frozen_string_literal: true # Representation of an enterprise being part of an order cycle. # # A producer can be part as supplier. The supplier's products can be selected to # be available in the order cycle (incoming products). # # A selling enterprise can be part as distributor. The order cycle then appears # in its shopfront. Any incoming product can be selected to be shown in the # shopfront (outgoing products). But the set of shown products can be smaller # than all incoming products. class Exchange < ApplicationRecord acts_as_taggable belongs_to :order_cycle belongs_to :sender, class_name: 'Enterprise' belongs_to :receiver, class_name: 'Enterprise' has_many :exchange_variants, dependent: :delete_all has_many :variants, through: :exchange_variants has_many :exchange_fees, dependent: :destroy has_many :enterprise_fees, through: :exchange_fees # Links to open backorders of a distributor (outgoing exchanges only) # Don't allow removal of distributor from OC while we have an open backorder. has_many :semantic_links, as: :subject, dependent: :restrict_with_error validates :sender_id, uniqueness: { scope: [:order_cycle_id, :receiver_id, :incoming] } before_destroy :delete_related_exchange_variants, prepend: true after_save :touch_receiver accepts_nested_attributes_for :variants scope :in_order_cycle, lambda { |order_cycle| where(order_cycle_id: order_cycle) } scope :incoming, -> { where(incoming: true) } scope :outgoing, -> { where(incoming: false) } scope :from_enterprise, lambda { |enterprise| where(sender_id: enterprise) } scope :to_enterprise, lambda { |enterprise| where(receiver_id: enterprise) } scope :from_enterprises, lambda { |enterprises| where(exchanges: { sender_id: enterprises }) } scope :to_enterprises, lambda { |enterprises| where(exchanges: { receiver_id: enterprises }) } scope :involving, lambda { |enterprises| where('exchanges.receiver_id IN (?) OR exchanges.sender_id IN (?)', enterprises, enterprises). select('DISTINCT exchanges.*') } scope :supplying_to, lambda { |distributor| where('exchanges.incoming OR exchanges.receiver_id = ?', distributor) } scope :with_variant, lambda { |variant| joins(:exchange_variants).where(exchange_variants: { variant_id: variant }) } scope :with_any_variant, lambda { |variant_ids| joins(:exchange_variants). where(exchange_variants: { variant_id: variant_ids }). select('DISTINCT exchanges.*') } scope :with_product, lambda { |product| joins(:exchange_variants). where(exchange_variants: { variant_id: product.variants.select(&:id) }) } scope :by_enterprise_name, -> { joins('INNER JOIN enterprises AS sender ON (sender.id = exchanges.sender_id)'). joins('INNER JOIN enterprises AS receiver ON (receiver.id = exchanges.receiver_id)'). order(Arel.sql("CASE WHEN exchanges.incoming='t' THEN sender.name ELSE receiver.name END")) } # Exchanges on order cycles that are dated and are upcoming or open are cached scope :cachable, -> { outgoing. joins(:order_cycle). merge(OrderCycle.dated). merge(OrderCycle.not_closed) } scope :managed_by, lambda { |user| if user.admin? where(nil) else joins("LEFT JOIN enterprises senders ON senders.id = exchanges.sender_id"). joins("LEFT JOIN enterprises receivers ON receivers.id = exchanges.receiver_id"). joins("LEFT JOIN enterprise_roles sender_roles ON sender_roles.enterprise_id = senders.id"). joins("LEFT JOIN enterprise_roles receiver_roles ON receiver_roles.enterprise_id = receivers.id"). where("sender_roles.user_id = ? AND receiver_roles.user_id = ?", user.id, user.id) end } def clone!(new_order_cycle) exchange = dup exchange.order_cycle = new_order_cycle exchange.enterprise_fee_ids = enterprise_fee_ids exchange.tag_list = tag_list exchange.save! clone_all_exchange_variants(exchange.id) exchange end def role incoming? ? 'supplier' : 'distributor' end def participant incoming? ? sender : receiver end def touch_receiver return unless receiver&.persisted? receiver.touch_later end private # An Order Cycle can have thousands of ExchangeVariants. # It's a simple association without any callbacks on creation. So we can # insert in bulk and improve the performance tenfold for large order cycles. def clone_all_exchange_variants(exchange_id) return unless variant_ids.any? ExchangeVariant.insert_all( # rubocop:disable Rails/SkipsModelValidations variant_ids.map{ |variant_id| { variant_id:, exchange_id: } } ) end def delete_related_exchange_variants return unless incoming? ExchangeVariant.where(variant_id: variant_ids). joins(:exchange). where(exchanges: { order_cycle:, incoming: false }). delete_all end end