cirandas.net

ref: master

plugins/suppliers/lib/ext/products_plugin/product.rb


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
require_dependency 'products_plugin/product'

ActiveSupport.on_load :solr_product do
  ::ProductsPlugin::Product.class_eval do

    def solr_supplied
      self.supplied?
    end

    self.solr_extra_fields << :solr_supplied
  end
end

module ProductsPlugin
  class Product

    attr_accessible :from_products, :from_product, :supplier_id, :supplier

    has_many :sources_from_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
    has_one  :sources_from_product,  foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
    has_many :sources_to_products, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
    has_one  :sources_to_product,  foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct'
    has_many :to_products, -> { order 'id ASC' }, through: :sources_to_products
    has_one  :to_product, -> { order 'id ASC' },  through: :sources_to_product,  autosave: true
    has_many :from_products, -> { order 'id ASC' }, through: :sources_from_products
    has_one  :from_product, -> { order 'id ASC' },  through: :sources_from_product, autosave: true

    has_many :sources_from_2x_products, through: :from_products, source: :sources_from_products
    has_one  :sources_from_2x_product,  through: :from_product,  source: :sources_from_product
    has_many :sources_to_2x_products,   through: :to_products,   source: :sources_to_products
    has_one  :sources_to_2x_product,    through: :to_product,    source: :sources_to_product
    has_many :from_2x_products, through: :sources_from_2x_products, source: :from_product
    has_one  :from_2x_product,  through: :sources_from_2x_product,  source: :from_product
    has_many :to_2x_products,   through: :sources_to_2x_products,   source: :to_product
    has_one  :to_2x_product,    through: :sources_to_2x_product,    source: :to_product

    # semantic alias for supplier_from_product(s)
    has_many :sources_supplier_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
    has_one  :sources_supplier_product,  foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
    has_many :supplier_products, -> { order 'id ASC' }, through: :sources_supplier_products, source: :from_product
    has_one  :supplier_product, -> { order 'id ASC' },  through: :sources_supplier_product,  source: :from_product, autosave: true
    has_many :suppliers, -> { distinct.order 'id ASC' }, through: :sources_supplier_products
    has_one  :supplier, -> { order 'id ASC' },  through: :sources_supplier_product

    has_many :consumers, -> { distinct.order 'id ASC' }, through: :to_products, source: :profile
    has_one  :consumer, -> { order 'id ASC' },  through: :to_product,  source: :profile

    # overhide original, FIXME: rename to available_and_supplier_active
    scope :available, -> {
      joins(:suppliers).
      where 'products.available = ? AND suppliers_plugin_suppliers.active = ?', true, true
    }
    scope :unavailable, -> {
      where 'products.available <> ? OR suppliers_plugin_suppliers.active <> ?', true, true
    }
    scope :with_available, -> (available) {
      op = if available then '=' else '<>' end
      cond = if available then 'AND' else 'OR' end
      where "products.available #{op} ? #{cond} suppliers_plugin_suppliers.active #{op} ?", true, true
    }

    scope :fp_name_like, -> (name) { where "from_products_products.name ILIKE ?", "%#{name}%" }
    scope :fp_with_product_category_id, -> (id) { where 'from_products_products.product_category_id = ?', id }

    # prefer #distributed_products has_many to use DistributedProduct scopes and eager loading
    scope :distributed, -> { where type: 'SuppliersPlugin::DistributedProduct'}
    scope :own, -> { where type: nil }
    scope :supplied, -> {
      # this remove duplicates and allow sorting on the fields, unlike distinct
      group('products.id').
      where type: [nil, 'SuppliersPlugin::DistributedProduct']
    }
    scope :supplied_for_count, -> {
      distinct.
      where type: [nil, 'SuppliersPlugin::DistributedProduct']
    }

    scope :from_supplier, -> (supplier) { joins(:suppliers).where 'suppliers_plugin_suppliers.id = ?', supplier.id }
    scope :from_supplier_id, -> (supplier_id) { joins(:suppliers).where 'suppliers_plugin_suppliers.id = ?', supplier_id }

    after_create :distribute_to_consumers

    def own?
      self.class == Product
    end
    def distributed?
      self.class == SuppliersPlugin::DistributedProduct
    end
    def supplied?
      self.own? or self.distributed?
    end

    def supplier
      # FIXME: use self.suppliers when rails support for nested preload comes
      @supplier ||= self.sources_supplier_product.supplier rescue nil
      @supplier ||= self.profile.self_supplier rescue nil
    end
    def supplier= value
      @supplier = value
    end
    def supplier_id
      self.supplier.id
    end
    def supplier_id= id
      @supplier = profile.environment.profiles.find id
    end

    def supplier_dummy?
      self.supplier ? self.supplier.dummy? : self.profile.dummy?
    end

    def distribute_to_consumer consumer, attrs = {}
      distributed_product = consumer.distributed_products.includes(:from_products).where(profile_id: consumer.id, from_products_products: {id: self.id}).first
      distributed_product ||= SuppliersPlugin::DistributedProduct.create! profile: consumer, from_product: self
      distributed_product.update! attrs if attrs.present?
      distributed_product
    end

    def destroy_dependent
      self.to_products.each do |to_product|
        to_product.destroy if to_product.respond_to? :dependent? and to_product.dependent?
      end
    end

    # before_destroy and after_destroy don't work,
    # see http://stackoverflow.com/questions/14175330/associations-not-loaded-in-before-destroy-callback
    def destroy
      self.class.transaction do
        self.destroy_dependent
        super
      end
    end

    def diff from = self.from_product
      return @changed_attrs if @changed_attrs
      @changed_attrs = []
      SuppliersPlugin::BaseProduct::CORE_DEFAULT_ATTRIBUTES.each do |attr|
        @changed_attrs << attr if self[attr].present? and self[attr] != from[attr]
      end
      @changed_attrs
    end

    protected

    def distribute_to_consumers
      # shopping_cart creates products without a profile...
      return unless self.profile

      self.profile.consumers.except_people.except_self.each do |consumer|
        self.distribute_to_consumer consumer.profile
      end
    end

  end
end