Commit 2b56f641a244024b17b41c3c2b7d93c37a960145
1 parent
74e81649
Exists in
send_email_to_admins
and in
5 other branches
products: fix dependent plugins
Showing
19 changed files
with
292 additions
and
276 deletions
Show diff stats
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 |
| ... | ... | @@ -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
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 |
| ... | ... | @@ -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 < 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 |
| ... | ... | @@ -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/suppliers/lib/ext/price_detail.rb
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 |
| ... | ... | @@ -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 | ... | ... |