base_product.rb
6.65 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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# for some unknown reason, if this is named SuppliersPlugin::Product then
# cycle.products will go to an infinite loop
class SuppliersPlugin::BaseProduct < Product
attr_accessible :default_margin_percentage, :margin_percentage, :default_unit, :unit_detail,
:supplier_product_attributes
accepts_nested_attributes_for :supplier_product
default_scope -> {
includes(
# from_products is required for products.available
:from_products,
# FIXME: move use cases to a scope called 'includes_for_links'
{suppliers: [{ profile: [:domains, {environment: :domains}] }]},
{profile: [:domains, {environment: :domains}]}
)
}
# if abstract_class is true then it will trigger https://github.com/rails/rails/issues/20871
#self.abstract_class = true
settings_items :minimum_selleable, type: Float, default: nil
settings_items :margin_percentage, type: Float, default: nil
settings_items :quantity, type: Float, default: nil
settings_items :unit_detail, type: String, default: nil
CORE_DEFAULT_ATTRIBUTES = [
:name, :description, :price, :unit_id, :product_category_id, :image_id,
]
DEFAULT_ATTRIBUTES = CORE_DEFAULT_ATTRIBUTES + [
:margin_percentage, :stored, :minimum_selleable, :unit_detail,
]
extend DefaultDelegate::ClassMethods
default_delegate_setting :name, to: :supplier_product
default_delegate_setting :description, to: :supplier_product
default_delegate_setting :qualifiers, to: :supplier_product
default_delegate :product_qualifiers, default_setting: :default_qualifiers, to: :supplier_product
default_delegate_setting :product_category, to: :supplier_product
default_delegate :product_category_id, default_setting: :default_product_category, to: :supplier_product
default_delegate_setting :image, to: :supplier_product, prefix: :_default
default_delegate :image_id, default_setting: :_default_image, to: :supplier_product
default_delegate_setting :unit, to: :supplier_product
default_delegate :unit_id, default_setting: :default_unit, to: :supplier_product
default_delegate_setting :margin_percentage, to: :profile,
default_if: -> { self.own_margin_percentage.blank? or self.own_margin_percentage.zero? }
default_delegate :price, default_setting: :default_margin_percentage, default_if: :equal?,
to: -> { self.supplier_product.price_with_discount if self.supplier_product }
default_delegate :unit_detail, default_setting: :default_unit, to: :supplier_product
default_delegate_setting :minimum_selleable, to: :supplier_product
extend CurrencyHelper::ClassMethods
has_currency :own_price
has_currency :original_price
has_number_with_locale :minimum_selleable
has_number_with_locale :own_minimum_selleable
has_number_with_locale :original_minimum_selleable
has_number_with_locale :quantity
has_number_with_locale :margin_percentage
has_number_with_locale :own_margin_percentage
has_number_with_locale :original_margin_percentage
def self.default_product_category environment
ProductCategory.top_level_for(environment).order('name ASC').first
end
def self.default_unit
Unit.new(singular: I18n.t('suppliers_plugin.models.product.unit'), plural: I18n.t('suppliers_plugin.models.product.units'))
end
# override SuppliersPlugin::BaseProduct
def self.search_scope scope, params
scope = scope.from_supplier_id params[:supplier_id] if params[:supplier_id].present?
scope = scope.with_available(if params[:available] == 'true' then true else false end) if params[:available].present?
scope = scope.fp_name_like params[:name] if params[:name].present?
scope = scope.fp_with_product_category_id params[:category_id] if params[:category_id].present?
scope
end
def self.orphans_ids
# FIXME: need references from rails4 to do it without raw query
result = self.connection.execute <<-SQL
SELECT products.id FROM products
LEFT OUTER JOIN suppliers_plugin_source_products ON suppliers_plugin_source_products.to_product_id = products.id
LEFT OUTER JOIN products from_products_products ON from_products_products.id = suppliers_plugin_source_products.from_product_id
WHERE products.type IN (#{(self.descendants << self).map{ |d| "'#{d}'" }.join(',')})
GROUP BY products.id HAVING count(from_products_products.id) = 0;
SQL
result.values
end
def self.archive_orphans
self.where(id: self.orphans_ids).find_each batch_size: 50 do |product|
# need full save to trigger search index
product.update archived: true
end
end
def buy_price
self.supplier_products.inject(0){ |sum, p| sum += p.price || 0 }
end
def buy_unit
#TODO: handle multiple products
(self.supplier_product.unit rescue nil) || self.class.default_unit
end
def available
self[:available]
end
def available_with_supplier
return self.available_without_supplier unless self.supplier
self.available_without_supplier and self.supplier.active rescue false
end
def chained_available
return self.available_without_supplier unless self.supplier_product
self.available_without_supplier and self.supplier_product.available and self.supplier.active rescue false
end
alias_method_chain :available, :supplier
def dependent?
self.from_products.length >= 1
end
def orphan?
!self.dependent?
end
def minimum_selleable
self[:minimum_selleable] || 0.1
end
def price_with_margins base_price = nil, margin_source = nil
margin_source ||= self
margin_percentage = margin_source.margin_percentage
margin_percentage ||= self.profile.margin_percentage if self.profile
base_price ||= 0
price = if margin_percentage and not base_price.zero?
base_price.to_f + (margin_percentage.to_f / 100) * base_price.to_f
else
self.price_with_default
end
price
end
def price_without_margins
self[:price] / (1 + self.margin_percentage/100)
end
# FIXME: move to core
# just in case the from_products is nil
def product_category_with_default
self.product_category_without_default or self.class.default_product_category(self.environment)
end
def product_category_id_with_default
self.product_category_id_without_default or self.product_category_with_default.id
end
alias_method_chain :product_category, :default
alias_method_chain :product_category_id, :default
# FIXME: move to core
def unit_with_default
self.unit_without_default or self.class.default_unit
end
alias_method_chain :unit, :default
# FIXME: move to core
def archive
self.update! archived: true
end
def unarchive
self.update! archived: false
end
protected
def validate_uniqueness_of_column_name?
false
end
# overhide Product's after_create callback to avoid infinite loop
def distribute_to_consumers
end
end