product.rb
7.2 KB
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
require_dependency 'product'
# FIXME: The lines bellow should be on the core
class Product
extend CurrencyHelper::ClassMethods
has_currency :price
has_currency :discount
scope :alphabetically, -> { order 'products.name ASC' }
scope :available, -> { where available: true }
scope :unavailable, -> { where 'products.available <> true' }
scope :archived, -> { where archived: true }
scope :unarchived, -> { where 'products.archived <> true' }
scope :with_available, -> (available) { where available: available }
scope :with_price, -> { where 'products.price > 0' }
# FIXME: transliterate input and name column
scope :name_like, -> (name) { where "name ILIKE ?", "%#{name}%" }
scope :with_product_category_id, -> (id) { where product_category_id: id }
scope :by_profile, -> (profile) { where profile_id: profile.id }
scope :by_profile_id, -> (profile_id) { where profile_id: profile_id }
def self.product_categories_of products
ProductCategory.find products.collect(&:product_category_id).compact.select{ |id| not id.zero? }
end
attr_accessible :external_id
settings_items :external_id, type: String, default: nil
# should be on core, used by SuppliersPlugin::Import
attr_accessible :price_details
end
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.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