Commit 2b56f641a244024b17b41c3c2b7d93c37a960145

Authored by Braulio Bhavamitra
1 parent 74e81649

products: fix dependent plugins

plugins/metadata/install.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +if ENV['CI']
  2 + system 'script/noosfero-plugins -q enable products'
  3 + exit $?.exitstatus
  4 +end
  5 +
... ...
plugins/metadata/lib/ext/product.rb
... ... @@ -1,34 +0,0 @@
1   -require_dependency 'product'
2   -
3   -class Product
4   -
5   - metadata_spec namespace: :og, tags: {
6   - type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:product] || :product },
7   - url: proc do |p, plugin|
8   - url = p.url.merge! profile: p.profile.identifier, og_type: plugin.context.params[:og_type]
9   - plugin.og_url_for url
10   - end,
11   - gr_hascurrencyvalue: proc{ |p, plugin| p.price.to_f },
12   - gr_hascurrency: proc{ |p, plugin| p.environment.currency_unit },
13   - title: proc{ |p, plugin| "#{p.name} - #{p.profile.name}" if p },
14   - description: proc{ |p, plugin| ActionView::Base.full_sanitizer.sanitize p.description },
15   -
16   - image: proc do |p, plugin|
17   - img = "#{p.environment.top_url}#{p.image.public_filename}".html_safe if p.image
18   - img = "#{p.environment.top_url}#{p.profile.image.public_filename}".html_safe if img.blank? and p.profile.image
19   - img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank?
20   - img
21   - end,
22   - 'image:type' => proc{ |p, plugin| p.image.content_type if p.image },
23   - 'image:height' => proc{ |p, plugin| p.image.height if p.image },
24   - 'image:width' => proc{ |p, plugin| p.image.width if p.image },
25   -
26   - see_also: [],
27   - site_name: proc{ |p, plugin| plugin.og_url_for p.profile.url },
28   - updated_time: proc{ |p, plugin| p.updated_at.iso8601 if p.updated_at },
29   -
30   - 'locale:locale' => proc{ |p, plugin| p.environment.default_language },
31   - 'locale:alternate' => proc{ |p, plugin| p.environment.languages - [p.environment.default_language] if p.environment.languages },
32   - }
33   -
34   -end
plugins/metadata/lib/ext/products_plugin/product.rb 0 → 100644
... ... @@ -0,0 +1,38 @@
  1 +if defined? ProductsPlugin
  2 + require_dependency 'products_plugin/product'
  3 +
  4 + module ProductsPlugin
  5 + class Product
  6 +
  7 + metadata_spec namespace: :og, tags: {
  8 + type: proc{ |p, plugin| plugin.context.params[:og_type] || MetadataPlugin.og_types[:product] || :product },
  9 + url: proc do |p, plugin|
  10 + url = p.url.merge! profile: p.profile.identifier, og_type: plugin.context.params[:og_type]
  11 + plugin.og_url_for url
  12 + end,
  13 + gr_hascurrencyvalue: proc{ |p, plugin| p.price.to_f },
  14 + gr_hascurrency: proc{ |p, plugin| p.environment.currency_unit },
  15 + title: proc{ |p, plugin| "#{p.name} - #{p.profile.name}" if p },
  16 + description: proc{ |p, plugin| ActionView::Base.full_sanitizer.sanitize p.description },
  17 +
  18 + image: proc do |p, plugin|
  19 + img = "#{p.environment.top_url}#{p.image.public_filename}".html_safe if p.image
  20 + img = "#{p.environment.top_url}#{p.profile.image.public_filename}".html_safe if img.blank? and p.profile.image
  21 + img ||= MetadataPlugin.config[:open_graph][:environment_logo] rescue nil if img.blank?
  22 + img
  23 + end,
  24 + 'image:type' => proc{ |p, plugin| p.image.content_type if p.image },
  25 + 'image:height' => proc{ |p, plugin| p.image.height if p.image },
  26 + 'image:width' => proc{ |p, plugin| p.image.width if p.image },
  27 +
  28 + see_also: [],
  29 + site_name: proc{ |p, plugin| plugin.og_url_for p.profile.url },
  30 + updated_time: proc{ |p, plugin| p.updated_at.iso8601 if p.updated_at },
  31 +
  32 + 'locale:locale' => proc{ |p, plugin| p.environment.default_language },
  33 + 'locale:alternate' => proc{ |p, plugin| p.environment.languages - [p.environment.default_language] if p.environment.languages },
  34 + }
  35 +
  36 + end
  37 + end
  38 +end
... ...
plugins/metadata/test/functional/manage_products_controller_test.rb
... ... @@ -1,24 +0,0 @@
1   -require 'test_helper'
2   -require 'home_controller'
3   -
4   -class ManageProductsControllerTest < ActionController::TestCase
5   -
6   - def setup
7   - @controller = ManageProductsController.new
8   - @request = ActionController::TestRequest.new
9   - @response = ActionController::TestResponse.new
10   - @enterprise = fast_create(Enterprise, name: 'test', identifier: 'test_ent')
11   - @user = create_user_with_permission('test_user', 'manage_products', @enterprise)
12   - @environment = @enterprise.environment
13   - @environment.enable('products_for_enterprises')
14   - login_as :test_user
15   -
16   - @environment.enabled_plugins += ['MetadataPlugin']
17   - @environment.save!
18   - end
19   -
20   - should "not crash on new products" do
21   - get :new, profile: @enterprise.identifier
22   - end
23   -
24   -end
plugins/metadata/test/functional/products_plugin/page_controller_test.rb 0 → 100644
... ... @@ -0,0 +1,24 @@
  1 +require 'test_helper'
  2 +
  3 +if defined? ProductsPlugin
  4 + module ProductsPlugin
  5 + class ManageProductsControllerTest < ActionController::TestCase
  6 +
  7 + def setup
  8 + @controller = PageController.new
  9 + @request = ActionController::TestRequest.new
  10 + @response = ActionController::TestResponse.new
  11 + @enterprise = fast_create(Enterprise, name: 'test', identifier: 'test_ent')
  12 + @user = create_user_with_permission('test_user', 'manage_products', @enterprise)
  13 + login_as :test_user
  14 +
  15 + @enterprise.environment.enable_plugin 'MetadataPlugin'
  16 + end
  17 +
  18 + should "not crash on new products" do
  19 + get :new, profile: @enterprise.identifier
  20 + end
  21 +
  22 + end
  23 + end
  24 +end
... ...
plugins/orders/install.rb
1   -system "script/noosfero-plugins -q enable delivery"
  1 +system 'script/noosfero-plugins -q enable products delivery'
2 2 exit $?.exitstatus
3 3  
... ...
plugins/orders/lib/ext/product.rb
... ... @@ -1,16 +0,0 @@
1   -require_dependency 'product'
2   -
3   -class Product
4   -
5   - has_many :items, class_name: 'OrdersPlugin::Item', foreign_key: :product_id, dependent: :destroy
6   -
7   - has_many :orders, through: :items
8   - has_many :sales, through: :items
9   - has_many :purchases, through: :items
10   -
11   - attr_accessor :quantity_ordered
12   -
13   - extend CurrencyHelper::ClassMethods
14   - instance_exec &OrdersPlugin::Item::DefineTotals
15   -
16   -end
plugins/orders/lib/ext/products_plugin/product.rb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +require_dependency 'products_plugin/product'
  2 +
  3 +module ProductsPlugin
  4 + class Product
  5 +
  6 + has_many :items, class_name: 'OrdersPlugin::Item', foreign_key: :product_id, dependent: :destroy
  7 +
  8 + has_many :orders, through: :items
  9 + has_many :sales, through: :items
  10 + has_many :purchases, through: :items
  11 +
  12 + attr_accessor :quantity_ordered
  13 +
  14 + extend CurrencyHelper::ClassMethods
  15 + instance_exec &OrdersPlugin::Item::DefineTotals
  16 +
  17 + end
  18 +end
... ...
plugins/orders/models/orders_plugin/item.rb
... ... @@ -39,7 +39,7 @@ class OrdersPlugin::Item &lt; ApplicationRecord
39 39 belongs_to :sale, class_name: '::OrdersPlugin::Sale', foreign_key: :order_id, touch: true
40 40 belongs_to :purchase, class_name: '::OrdersPlugin::Purchase', foreign_key: :order_id, touch: true
41 41  
42   - belongs_to :product
  42 + belongs_to :product, class_name: '::ProductsPlugin::Product'
43 43 has_one :supplier, through: :product
44 44  
45 45 has_one :profile, through: :order
... ...
plugins/orders_cycle/lib/ext/product.rb
... ... @@ -1,12 +0,0 @@
1   -require_dependency 'product'
2   -
3   -# based on orders/lib/ext/product.rb
4   -class Product
5   -
6   - has_many :orders_cycles_items, class_name: 'OrdersCyclePlugin::Item', foreign_key: :product_id
7   -
8   - has_many :orders_cycles_orders, through: :orders_cycles_items, source: :order
9   - has_many :orders_cycles_sales, through: :orders_cycles_items, source: :sale
10   - has_many :orders_cycles_purchases, through: :orders_cycles_items, source: :purchase
11   -
12   -end
plugins/orders_cycle/lib/ext/products_plugin/product.rb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +require_dependency 'products_plugin/product'
  2 +
  3 +# based on orders/lib/ext/product.rb
  4 +module ProductsPlugin
  5 + class Product
  6 +
  7 + has_many :orders_cycles_items, class_name: 'OrdersCyclePlugin::Item', foreign_key: :product_id
  8 +
  9 + has_many :orders_cycles_orders, through: :orders_cycles_items, source: :order
  10 + has_many :orders_cycles_sales, through: :orders_cycles_items, source: :sale
  11 + has_many :orders_cycles_purchases, through: :orders_cycles_items, source: :purchase
  12 +
  13 + end
  14 +end
... ...
plugins/pg_search/db/migrate/20130320010063_create_indexes_for_search.rb
1 1 class CreateIndexesForSearch < ActiveRecord::Migration
2   - SEARCHABLES = %w[ article comment qualifier national_region certifier profile license scrap category ]
  2 + SEARCHABLES = %w[ article comment national_region profile license scrap category ]
3 3 KLASSES = SEARCHABLES.map {|searchable| searchable.camelize.constantize }
4 4 def self.up
5 5 KLASSES.each do |klass|
... ...
plugins/shopping_cart/views/shopping_cart_plugin/send_request.js.erb
1 1 <% if @success %>
2   - window.location.href = <%= url_for(controller: :catalog, profile: @profile.identifier).to_json %>
  2 + window.location.href = <%= url_for(controller: 'products_plugin/catalog', profile: @profile.identifier).to_json %>
3 3 <% else %>
4 4 display_notice(<%= "#{_ 'Error'}: #{@error}".to_json %>)
5 5 <% end %>
... ...
plugins/sniffer/install.rb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +system 'script/noosfero-plugins -q enable products'
  2 +exit $?.exitstatus
  3 +
... ...
plugins/statistics/install.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +if ENV['CI']
  2 + system 'script/noosfero-plugins -q enable products'
  3 + exit $?.exitstatus
  4 +end
  5 +
... ...
plugins/suppliers/install.rb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +system 'script/noosfero-plugins -q enable products'
  2 +exit $?.exitstatus
  3 +
... ...
plugins/suppliers/lib/ext/price_detail.rb
... ... @@ -1,8 +0,0 @@
1   -require_dependency 'price_detail'
2   -
3   -class PriceDetail
4   -
5   - # should be on core, used by SuppliersPlugin::Import
6   - attr_accessible :production_cost
7   -
8   -end
plugins/suppliers/lib/ext/product.rb
... ... @@ -1,178 +0,0 @@
1   -require_dependency 'product'
2   -
3   -# FIXME: The lines bellow should be on the core
4   -class Product
5   -
6   - extend CurrencyHelper::ClassMethods
7   - has_currency :price
8   - has_currency :discount
9   -
10   - scope :alphabetically, -> { order 'products.name ASC' }
11   -
12   - scope :available, -> { where available: true }
13   - scope :unavailable, -> { where 'products.available <> true' }
14   - scope :archived, -> { where archived: true }
15   - scope :unarchived, -> { where 'products.archived <> true' }
16   -
17   - scope :with_available, -> (available) { where available: available }
18   - scope :with_price, -> { where 'products.price > 0' }
19   -
20   - # FIXME: transliterate input and name column
21   - scope :name_like, -> (name) { where "name ILIKE ?", "%#{name}%" }
22   - scope :with_product_category_id, -> (id) { where product_category_id: id }
23   -
24   - scope :by_profile, -> (profile) { where profile_id: profile.id }
25   - scope :by_profile_id, -> (profile_id) { where profile_id: profile_id }
26   -
27   - def self.product_categories_of products
28   - ProductCategory.find products.collect(&:product_category_id).compact.select{ |id| not id.zero? }
29   - end
30   -
31   - attr_accessible :external_id
32   - settings_items :external_id, type: String, default: nil
33   -
34   - # should be on core, used by SuppliersPlugin::Import
35   - attr_accessible :price_details
36   -
37   -end
38   -
39   -class Product
40   -
41   - attr_accessible :from_products, :from_product, :supplier_id, :supplier
42   -
43   - has_many :sources_from_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
44   - has_one :sources_from_product, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
45   - has_many :sources_to_products, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
46   - has_one :sources_to_product, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct'
47   - has_many :to_products, -> { order 'id ASC' }, through: :sources_to_products
48   - has_one :to_product, -> { order 'id ASC' }, through: :sources_to_product, autosave: true
49   - has_many :from_products, -> { order 'id ASC' }, through: :sources_from_products
50   - has_one :from_product, -> { order 'id ASC' }, through: :sources_from_product, autosave: true
51   -
52   - has_many :sources_from_2x_products, through: :from_products, source: :sources_from_products
53   - has_one :sources_from_2x_product, through: :from_product, source: :sources_from_product
54   - has_many :sources_to_2x_products, through: :to_products, source: :sources_to_products
55   - has_one :sources_to_2x_product, through: :to_product, source: :sources_to_product
56   - has_many :from_2x_products, through: :sources_from_2x_products, source: :from_product
57   - has_one :from_2x_product, through: :sources_from_2x_product, source: :from_product
58   - has_many :to_2x_products, through: :sources_to_2x_products, source: :to_product
59   - has_one :to_2x_product, through: :sources_to_2x_product, source: :to_product
60   -
61   - # semantic alias for supplier_from_product(s)
62   - has_many :sources_supplier_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
63   - has_one :sources_supplier_product, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
64   - has_many :supplier_products, -> { order 'id ASC' }, through: :sources_supplier_products, source: :from_product
65   - has_one :supplier_product, -> { order 'id ASC' }, through: :sources_supplier_product, source: :from_product, autosave: true
66   - has_many :suppliers, -> { distinct.order 'id ASC' }, through: :sources_supplier_products
67   - has_one :supplier, -> { order 'id ASC' }, through: :sources_supplier_product
68   -
69   - has_many :consumers, -> { distinct.order 'id ASC' }, through: :to_products, source: :profile
70   - has_one :consumer, -> { order 'id ASC' }, through: :to_product, source: :profile
71   -
72   - # overhide original, FIXME: rename to available_and_supplier_active
73   - scope :available, -> {
74   - joins(:suppliers).
75   - where 'products.available = ? AND suppliers_plugin_suppliers.active = ?', true, true
76   - }
77   - scope :unavailable, -> {
78   - where 'products.available <> ? OR suppliers_plugin_suppliers.active <> ?', true, true
79   - }
80   - scope :with_available, -> (available) {
81   - op = if available then '=' else '<>' end
82   - cond = if available then 'AND' else 'OR' end
83   - where "products.available #{op} ? #{cond} suppliers_plugin_suppliers.active #{op} ?", true, true
84   - }
85   -
86   - scope :fp_name_like, -> (name) { where "from_products_products.name ILIKE ?", "%#{name}%" }
87   - scope :fp_with_product_category_id, -> (id) { where 'from_products_products.product_category_id = ?', id }
88   -
89   - # prefer distributed_products has_many to use DistributedProduct scopes and eager loading
90   - scope :distributed, -> { where type: 'SuppliersPlugin::DistributedProduct'}
91   - scope :own, -> { where type: nil }
92   - scope :supplied, -> {
93   - # this remove duplicates and allow sorting on the fields, unlike distinct
94   - group('products.id').
95   - where type: [nil, 'SuppliersPlugin::DistributedProduct']
96   - }
97   - scope :supplied_for_count, -> {
98   - distinct.
99   - where type: [nil, 'SuppliersPlugin::DistributedProduct']
100   - }
101   -
102   - scope :from_supplier, -> (supplier) { joins(:suppliers).where 'suppliers_plugin_suppliers.id = ?', supplier.id }
103   - scope :from_supplier_id, -> (supplier_id) { joins(:suppliers).where 'suppliers_plugin_suppliers.id = ?', supplier_id }
104   -
105   - after_create :distribute_to_consumers
106   -
107   - def own?
108   - self.class == Product
109   - end
110   - def distributed?
111   - self.class == SuppliersPlugin::DistributedProduct
112   - end
113   - def supplied?
114   - self.own? or self.distributed?
115   - end
116   -
117   - def supplier
118   - # FIXME: use self.suppliers when rails support for nested preload comes
119   - @supplier ||= self.sources_supplier_product.supplier rescue nil
120   - @supplier ||= self.profile.self_supplier rescue nil
121   - end
122   - def supplier= value
123   - @supplier = value
124   - end
125   - def supplier_id
126   - self.supplier.id
127   - end
128   - def supplier_id= id
129   - @supplier = profile.environment.profiles.find id
130   - end
131   -
132   - def supplier_dummy?
133   - self.supplier ? self.supplier.dummy? : self.profile.dummy?
134   - end
135   -
136   - def distribute_to_consumer consumer, attrs = {}
137   - distributed_product = consumer.distributed_products.where(profile_id: consumer.id, from_products_products: {id: self.id}).first
138   - distributed_product ||= SuppliersPlugin::DistributedProduct.create! profile: consumer, from_product: self
139   - distributed_product.update! attrs if attrs.present?
140   - distributed_product
141   - end
142   -
143   - def destroy_dependent
144   - self.to_products.each do |to_product|
145   - to_product.destroy if to_product.respond_to? :dependent? and to_product.dependent?
146   - end
147   - end
148   -
149   - # before_destroy and after_destroy don't work,
150   - # see http://stackoverflow.com/questions/14175330/associations-not-loaded-in-before-destroy-callback
151   - def destroy
152   - self.class.transaction do
153   - self.destroy_dependent
154   - super
155   - end
156   - end
157   -
158   - def diff from = self.from_product
159   - return @changed_attrs if @changed_attrs
160   - @changed_attrs = []
161   - SuppliersPlugin::BaseProduct::CORE_DEFAULT_ATTRIBUTES.each do |attr|
162   - @changed_attrs << attr if self[attr].present? and self[attr] != from[attr]
163   - end
164   - @changed_attrs
165   - end
166   -
167   - protected
168   -
169   - def distribute_to_consumers
170   - # shopping_cart creates products without a profile...
171   - return unless self.profile
172   -
173   - self.profile.consumers.except_people.except_self.each do |consumer|
174   - self.distribute_to_consumer consumer.profile
175   - end
176   - end
177   -
178   -end
plugins/suppliers/lib/ext/products_plugin/product.rb 0 → 100644
... ... @@ -0,0 +1,178 @@
  1 +require_dependency 'products_plugin/product'
  2 +
  3 +# FIXME: The lines bellow should be on the core
  4 +class Product
  5 +
  6 + extend CurrencyHelper::ClassMethods
  7 + has_currency :price
  8 + has_currency :discount
  9 +
  10 + scope :alphabetically, -> { order 'products.name ASC' }
  11 +
  12 + scope :available, -> { where available: true }
  13 + scope :unavailable, -> { where 'products.available <> true' }
  14 + scope :archived, -> { where archived: true }
  15 + scope :unarchived, -> { where 'products.archived <> true' }
  16 +
  17 + scope :with_available, -> (available) { where available: available }
  18 + scope :with_price, -> { where 'products.price > 0' }
  19 +
  20 + # FIXME: transliterate input and name column
  21 + scope :name_like, -> (name) { where "name ILIKE ?", "%#{name}%" }
  22 + scope :with_product_category_id, -> (id) { where product_category_id: id }
  23 +
  24 + scope :by_profile, -> (profile) { where profile_id: profile.id }
  25 + scope :by_profile_id, -> (profile_id) { where profile_id: profile_id }
  26 +
  27 + def self.product_categories_of products
  28 + ProductCategory.find products.collect(&:product_category_id).compact.select{ |id| not id.zero? }
  29 + end
  30 +
  31 + attr_accessible :external_id
  32 + settings_items :external_id, type: String, default: nil
  33 +
  34 + # should be on core, used by SuppliersPlugin::Import
  35 + attr_accessible :price_details
  36 +
  37 +end
  38 +
  39 +class Product
  40 +
  41 + attr_accessible :from_products, :from_product, :supplier_id, :supplier
  42 +
  43 + has_many :sources_from_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
  44 + has_one :sources_from_product, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  45 + has_many :sources_to_products, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct', dependent: :destroy
  46 + has_one :sources_to_product, foreign_key: :from_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  47 + has_many :to_products, -> { order 'id ASC' }, through: :sources_to_products
  48 + has_one :to_product, -> { order 'id ASC' }, through: :sources_to_product, autosave: true
  49 + has_many :from_products, -> { order 'id ASC' }, through: :sources_from_products
  50 + has_one :from_product, -> { order 'id ASC' }, through: :sources_from_product, autosave: true
  51 +
  52 + has_many :sources_from_2x_products, through: :from_products, source: :sources_from_products
  53 + has_one :sources_from_2x_product, through: :from_product, source: :sources_from_product
  54 + has_many :sources_to_2x_products, through: :to_products, source: :sources_to_products
  55 + has_one :sources_to_2x_product, through: :to_product, source: :sources_to_product
  56 + has_many :from_2x_products, through: :sources_from_2x_products, source: :from_product
  57 + has_one :from_2x_product, through: :sources_from_2x_product, source: :from_product
  58 + has_many :to_2x_products, through: :sources_to_2x_products, source: :to_product
  59 + has_one :to_2x_product, through: :sources_to_2x_product, source: :to_product
  60 +
  61 + # semantic alias for supplier_from_product(s)
  62 + has_many :sources_supplier_products, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  63 + has_one :sources_supplier_product, foreign_key: :to_product_id, class_name: 'SuppliersPlugin::SourceProduct'
  64 + has_many :supplier_products, -> { order 'id ASC' }, through: :sources_supplier_products, source: :from_product
  65 + has_one :supplier_product, -> { order 'id ASC' }, through: :sources_supplier_product, source: :from_product, autosave: true
  66 + has_many :suppliers, -> { distinct.order 'id ASC' }, through: :sources_supplier_products
  67 + has_one :supplier, -> { order 'id ASC' }, through: :sources_supplier_product
  68 +
  69 + has_many :consumers, -> { distinct.order 'id ASC' }, through: :to_products, source: :profile
  70 + has_one :consumer, -> { order 'id ASC' }, through: :to_product, source: :profile
  71 +
  72 + # overhide original, FIXME: rename to available_and_supplier_active
  73 + scope :available, -> {
  74 + joins(:suppliers).
  75 + where 'products.available = ? AND suppliers_plugin_suppliers.active = ?', true, true
  76 + }
  77 + scope :unavailable, -> {
  78 + where 'products.available <> ? OR suppliers_plugin_suppliers.active <> ?', true, true
  79 + }
  80 + scope :with_available, -> (available) {
  81 + op = if available then '=' else '<>' end
  82 + cond = if available then 'AND' else 'OR' end
  83 + where "products.available #{op} ? #{cond} suppliers_plugin_suppliers.active #{op} ?", true, true
  84 + }
  85 +
  86 + scope :fp_name_like, -> (name) { where "from_products_products.name ILIKE ?", "%#{name}%" }
  87 + scope :fp_with_product_category_id, -> (id) { where 'from_products_products.product_category_id = ?', id }
  88 +
  89 + # prefer distributed_products has_many to use DistributedProduct scopes and eager loading
  90 + scope :distributed, -> { where type: 'SuppliersPlugin::DistributedProduct'}
  91 + scope :own, -> { where type: nil }
  92 + scope :supplied, -> {
  93 + # this remove duplicates and allow sorting on the fields, unlike distinct
  94 + group('products.id').
  95 + where type: [nil, 'SuppliersPlugin::DistributedProduct']
  96 + }
  97 + scope :supplied_for_count, -> {
  98 + distinct.
  99 + where type: [nil, 'SuppliersPlugin::DistributedProduct']
  100 + }
  101 +
  102 + scope :from_supplier, -> (supplier) { joins(:suppliers).where 'suppliers_plugin_suppliers.id = ?', supplier.id }
  103 + scope :from_supplier_id, -> (supplier_id) { joins(:suppliers).where 'suppliers_plugin_suppliers.id = ?', supplier_id }
  104 +
  105 + after_create :distribute_to_consumers
  106 +
  107 + def own?
  108 + self.class == Product
  109 + end
  110 + def distributed?
  111 + self.class == SuppliersPlugin::DistributedProduct
  112 + end
  113 + def supplied?
  114 + self.own? or self.distributed?
  115 + end
  116 +
  117 + def supplier
  118 + # FIXME: use self.suppliers when rails support for nested preload comes
  119 + @supplier ||= self.sources_supplier_product.supplier rescue nil
  120 + @supplier ||= self.profile.self_supplier rescue nil
  121 + end
  122 + def supplier= value
  123 + @supplier = value
  124 + end
  125 + def supplier_id
  126 + self.supplier.id
  127 + end
  128 + def supplier_id= id
  129 + @supplier = profile.environment.profiles.find id
  130 + end
  131 +
  132 + def supplier_dummy?
  133 + self.supplier ? self.supplier.dummy? : self.profile.dummy?
  134 + end
  135 +
  136 + def distribute_to_consumer consumer, attrs = {}
  137 + distributed_product = consumer.distributed_products.where(profile_id: consumer.id, from_products_products: {id: self.id}).first
  138 + distributed_product ||= SuppliersPlugin::DistributedProduct.create! profile: consumer, from_product: self
  139 + distributed_product.update! attrs if attrs.present?
  140 + distributed_product
  141 + end
  142 +
  143 + def destroy_dependent
  144 + self.to_products.each do |to_product|
  145 + to_product.destroy if to_product.respond_to? :dependent? and to_product.dependent?
  146 + end
  147 + end
  148 +
  149 + # before_destroy and after_destroy don't work,
  150 + # see http://stackoverflow.com/questions/14175330/associations-not-loaded-in-before-destroy-callback
  151 + def destroy
  152 + self.class.transaction do
  153 + self.destroy_dependent
  154 + super
  155 + end
  156 + end
  157 +
  158 + def diff from = self.from_product
  159 + return @changed_attrs if @changed_attrs
  160 + @changed_attrs = []
  161 + SuppliersPlugin::BaseProduct::CORE_DEFAULT_ATTRIBUTES.each do |attr|
  162 + @changed_attrs << attr if self[attr].present? and self[attr] != from[attr]
  163 + end
  164 + @changed_attrs
  165 + end
  166 +
  167 + protected
  168 +
  169 + def distribute_to_consumers
  170 + # shopping_cart creates products without a profile...
  171 + return unless self.profile
  172 +
  173 + self.profile.consumers.except_people.except_self.each do |consumer|
  174 + self.distribute_to_consumer consumer.profile
  175 + end
  176 + end
  177 +
  178 +end
... ...