Class: ProductNatureVariant

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, Customizable
Defined in:
app/models/product_nature_variant.rb

Overview

Informations

License

Ekylibre - Simple agricultural ERP Copyright (C) 2008-2009 Brice Texier, Thibaud Merigon Copyright (C) 2010-2012 Brice Texier Copyright (C) 2012-2016 Brice Texier, David Joulin

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see www.gnu.org/licenses.

Table: product_nature_variants

active               :boolean          default(FALSE), not null
category_id          :integer          not null
created_at           :datetime         not null
creator_id           :integer
custom_fields        :jsonb
derivative_of        :string
id                   :integer          not null, primary key
lock_version         :integer          default(0), not null
name                 :string
nature_id            :integer          not null
number               :string
picture_content_type :string
picture_file_name    :string
picture_file_size    :integer
picture_updated_at   :datetime
reference_name       :string
unit_name            :string           not null
updated_at           :datetime         not null
updater_id           :integer
variety              :string           not null

Defined Under Namespace

Classes: Item

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Customizable

#custom_value, #set_custom_value, #validate_custom_fields

Methods inherited from Ekylibre::Record::Base

#already_updated?, attr_readonly_with_conditions, #check_if_destroyable?, #check_if_updateable?, columns_definition, complex_scopes, customizable?, #customizable?, #customized?, #destroyable?, #editable?, has_picture, #human_attribute_name, human_attribute_name_with_id, nomenclature_reflections, #old_record, #others, refers_to, scope_with_registration, simple_scopes, #updateable?

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args) ⇒ Object

Get indicator value if option :at specify at which moment if option :reading is true, it returns the ProductNatureVariantReading record if option :interpolate is true, it returns the interpolated value :interpolate and :reading options are incompatible


271
272
273
274
# File 'app/models/product_nature_variant.rb', line 271

def method_missing(method_name, *args)
  return super unless Nomen::Indicator.items[method_name]
  get(method_name)
end

Class Method Details

.find_or_import!(variety, options = {}) ⇒ Object

Find or import variant from nomenclature with given attributes variety and derivative_of only are accepted for now


344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/models/product_nature_variant.rb', line 344

def find_or_import!(variety, options = {})
  variants = of_variety(variety)
  if derivative_of = options[:derivative_of]
    variants = variants.derivative_of(derivative_of)
  end
  if variants.empty?
    # Filter and imports
    filtereds = flattened_nomenclature.select do |item|
      item.variety >= variety &&
        ((derivative_of && item.derivative_of && item.derivative_of >= derivative_of) || (derivative_of.blank? && item.derivative_of.blank?))
    end
    filtereds.each do |item|
      import_from_nomenclature(item.name)
    end
  end
  variants.reload
end

.flattened_nomenclatureObject

Returns core attributes of nomenclature merge with nature if necessary name, variety, derivative_od, abilities


366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'app/models/product_nature_variant.rb', line 366

def flattened_nomenclature
  @flattened_nomenclature ||= Nomen::ProductNatureVariant.list.collect do |item|
    nature = Nomen::ProductNature[item.nature]
    f = (nature.frozen_indicators || []).map(&:to_sym)
    v = (nature.variable_indicators || []).map(&:to_sym)
    Item.new(
      item.name,
      Nomen::Variety.find(item.variety || nature.variety),
      Nomen::Variety.find(item.derivative_of || nature.derivative_of),
      WorkingSet::AbilityArray.load(nature.abilities),
      f + v, f, v
    )
  end
end

.import_from_nomenclature(reference_name, force = false) ⇒ Object

Load a product nature variant from product nature variant nomenclature


390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'app/models/product_nature_variant.rb', line 390

def import_from_nomenclature(reference_name, force = false)
  unless item = Nomen::ProductNatureVariant[reference_name]
    raise ArgumentError, "The product_nature_variant #{reference_name.inspect} is not known"
  end
  unless nature_item = Nomen::ProductNature[item.nature]
    raise ArgumentError, "The nature of the product_nature_variant #{item.nature.inspect} is not known"
  end
  unless !force && variant = ProductNatureVariant.find_by(reference_name: reference_name.to_s)
    attributes = {
      name: item.human_name,
      active: true,
      nature: ProductNature.import_from_nomenclature(item.nature),
      reference_name: item.name,
      unit_name: I18n.translate("nomenclatures.product_nature_variants.choices.unit_name.#{item.unit_name}"),
      # :frozen_indicators => item.frozen_indicators_values.to_s,
      variety: item.variety || nil,
      derivative_of: item.derivative_of || nil
    }
    variant = new(attributes)
    # puts variant.name.inspect.green
    unless variant.save
      raise "Cannot import variant #{reference_name.inspect}: #{variant.errors.full_messages.join(', ')}"
    end

  end

  unless item.frozen_indicators_values.to_s.blank?
    # create frozen indicator for each pair indicator, value ":population => 1unity"
    item.frozen_indicators_values.to_s.strip.split(/[[:space:]]*\,[[:space:]]*/)
        .collect { |i| i.split(/[[:space:]]*\:[[:space:]]*/) }.each do |i|
      # puts i.first.strip.downcase.to_sym.inspect.red
      variant.read!(i.first.strip.downcase.to_sym, i.second)
    end
  end

  variant
end

.items_of_expression(expression) ⇒ Object

Lists ProductNatureVariant::Item which match given expression Fully compatible with WSQL


383
384
385
386
387
# File 'app/models/product_nature_variant.rb', line 383

def items_of_expression(expression)
  flattened_nomenclature.select do |item|
    WorkingSet.check_record(expression, item)
  end
end

.load_defaults(_options = {}) ⇒ Object


428
429
430
431
432
# File 'app/models/product_nature_variant.rb', line 428

def load_defaults(_options = {})
  Nomen::ProductNatureVariant.all.flatten.collect do |p|
    import_from_nomenclature(p.to_s)
  end
end

Instance Method Details

#add_products(products, options = {}) ⇒ Object

add animals to new variant


146
147
148
149
150
151
152
153
154
155
# File 'app/models/product_nature_variant.rb', line 146

def add_products(products, options = {})
  Intervention.write(:product_evolution, options) do |i|
    i.cast :variant, self, as: 'product_evolution-variant'
    products.each do |p|
      product = (p.is_a?(Product) ? p : Product.find(p))
      member = i.cast :product, product, as: 'product_evolution-target'
      i.variant_cast :variant, member
    end
  end
end

#create_product!(attributes = {}) ⇒ Object

Shortcut for creating a new product of the variant


288
289
290
291
292
293
294
# File 'app/models/product_nature_variant.rb', line 288

def create_product!(attributes = {})
  attributes[:initial_owner] ||= Entity.of_company
  attributes[:initial_born_at] ||= Time.zone.now
  attributes[:born_at] ||= attributes[:initial_born_at]
  attributes[:name] ||= "#{name} (#{attributes[:initial_born_at].to_date.l})"
  matching_model.create!(attributes.merge(variant: self))
end

#current_outgoing_stock_ordered_not_deliveredObject

Return current quantity of all products link to the variant currently ordered or invoiced but not delivered


325
326
327
328
329
# File 'app/models/product_nature_variant.rb', line 325

def current_outgoing_stock_ordered_not_delivered
  sales = Sale.where(state: %w(order invoice))
  sale_items = SaleItem.where(variant_id: id, sale_id: sales.pluck(:id)).includes(:parcel_items).where(parcel_items: { sale_item_id: nil })
  sale_items.map(&:quantity).compact.sum.to_f
end

#current_stockObject

Return current stock of all products link to the variant


320
321
322
# File 'app/models/product_nature_variant.rb', line 320

def current_stock
  products.map(&:population).compact.sum.to_f
end

#default_catalog_item(usage) ⇒ Object

Returns item from default catalog for given usage


201
202
203
204
# File 'app/models/product_nature_variant.rb', line 201

def default_catalog_item(usage)
  catalog = Catalog.by_default!(usage)
  catalog.items.find_by(variant: self)
end

#generate(*args) ⇒ Object


276
277
278
279
280
281
282
283
284
285
# File 'app/models/product_nature_variant.rb', line 276

def generate(*args)
  options = args.extract_options!
  product_name = args.shift || options[:name]
  born_at = args.shift || options[:born_at]
  default_storage = args.shift || options[:default_storage]

  product_model = nature.matching_model

  product_model.create!(variant: self, name: product_name + ' ' + born_at.l, initial_owner: Entity.of_company, initial_born_at: born_at, default_storage: default_storage)
end

#get(indicator) ⇒ Object

Returns the direct value of an indicator of variant


177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/models/product_nature_variant.rb', line 177

def get(indicator)
  unless indicator.is_a?(Nomen::Item) || indicator = Nomen::Indicator[indicator]
    raise ArgumentError, "Unknown indicator #{indicator.inspect}. Expecting one of them: #{Nomen::Indicator.all.sort.to_sentence}."
  end
  if reading = reading(indicator.name)
    return reading.value
  elsif indicator.datatype == :measure
    return 0.0.in(indicator.unit)
  elsif indicator.datatype == :decimal
    return 0.0
  end
  nil
end

#has_frozen_indicator?(indicator) ⇒ Boolean

check if a variant has an indicator which is frozen or not

Returns:

  • (Boolean)

192
193
194
195
196
197
198
# File 'app/models/product_nature_variant.rb', line 192

def has_frozen_indicator?(indicator)
  if indicator.is_a?(Nomen::Item)
    return frozen_indicators.include?(indicator)
  else
    return frozen_indicators_list.include?(indicator)
  end
end

#last_purchase_item_for(supplier = nil) ⇒ Object

Returns last purchase item for the variant and a given supplier if any, or nil if there's no purchase item matching criterias


311
312
313
314
315
316
317
# File 'app/models/product_nature_variant.rb', line 311

def last_purchase_item_for(supplier = nil)
  return purchase_items.last unless supplier.present?
  purchase_items
    .joins(:purchase)
    .where('purchases.supplier_id = ?', Entity.find(supplier).id)
    .last
end

#picture_path(style = :original) ⇒ Object


331
332
333
# File 'app/models/product_nature_variant.rb', line 331

def picture_path(style = :original)
  picture.path(style)
end

#quantifiersObject

Returns a list of couple indicator/unit usable for the given variant The result is only based on measure indicators


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/models/product_nature_variant.rb', line 208

def quantifiers
  list = []
  indicators.each do |indicator|
    next unless indicator.gathering == :proportional_to_population
    if indicator.datatype == :measure
      Measure.siblings(indicator.unit).each do |unit_name|
        list << "#{indicator.name}/#{unit_name}"
      end
    elsif indicator.datatype == :integer || indicator.datatype == :decimal
      list << indicator.name.to_s
    end
  end
  variety = Nomen::Variety.find(self.variety)
  # Specials indicators
  if variety <= :product_group
    list << 'members_count' unless list.include?('members_count/unity')
    if variety <= :animal_group
      list << 'members_livestock_unit' unless list.include?('members_livestock_unit/unity')
    end
    list << 'members_population' unless list.include?('members_population/unity')
  end
  list
end

#read!(indicator, value, _options = {}) ⇒ Object

Measure a product for a given indicator


158
159
160
161
162
163
164
165
166
# File 'app/models/product_nature_variant.rb', line 158

def read!(indicator, value, _options = {})
  unless indicator.is_a?(Nomen::Item) || indicator = Nomen::Indicator[indicator]
    raise ArgumentError, "Unknown indicator #{indicator.inspect}. Expecting one of them: #{Nomen::Indicator.all.sort.to_sentence}."
  end
  reading = readings.find_or_initialize_by(indicator_name: indicator.name)
  reading.value = value
  reading.save!
  reading
end

#reading(indicator) ⇒ Object

Return the reading


169
170
171
172
173
174
# File 'app/models/product_nature_variant.rb', line 169

def reading(indicator)
  unless indicator.is_a?(Nomen::Item) || indicator = Nomen::Indicator[indicator]
    raise ArgumentError, "Unknown indicator #{indicator.inspect}. Expecting one of them: #{Nomen::Indicator.all.sort.to_sentence}."
  end
  readings.find_by(indicator_name: indicator.name)
end

#take(quantity) ⇒ Object


296
297
298
299
300
301
302
# File 'app/models/product_nature_variant.rb', line 296

def take(quantity)
  products.mine.reduce({}) do |result, product|
    reminder = quantity - result.values.sum
    result[product] = [product.population, reminder].min if reminder > 0
    result
  end
end

#take!(quantity) ⇒ Object


304
305
306
# File 'app/models/product_nature_variant.rb', line 304

def take!(quantity)
  raise 'errors.not_enough'.t if take(quantity).values.sum < quantity
end

#unified_quantifiers(options = {}) ⇒ Object

Returns a list of quantifier


233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'app/models/product_nature_variant.rb', line 233

def unified_quantifiers(options = {})
  list = quantifiers.map do |quantifier|
    pair = quantifier.split('/')
    indicator = Nomen::Indicator.find(pair.first)
    unit = (pair.second.blank? ? nil : Nomen::Unit.find(pair.second))
    hash = { indicator: { name: indicator.name, human_name: indicator.human_name } }
    hash[:unit] = if unit
                    { name: unit.name, symbol: unit.symbol, human_name: unit.human_name }
                  elsif indicator.name =~ /^members\_/
                    unit = Nomen::Unit.find(:unity)
                    { name: '', symbol: unit.symbol, human_name: unit.human_name }
                  else
                    { name: '', symbol: unit_name, human_name: unit_name }
                  end
    hash
  end

  # Add population
  if options[:population]
    # indicator = Nomen::Indicator[:population]
    list << { indicator: { name: :population, human_name: Product.human_attribute_name(:population) }, unit: { name: '', symbol: unit_name, human_name: unit_name } }
  end

  # Add working duration (intervention durations)
  if options[:working_duration]
    Nomen::Unit.where(dimension: :time).find_each do |unit|
      list << { indicator: { name: :working_duration, human_name: :working_duration.tl }, unit: { name: unit.name, symbol: unit.symbol, human_name: unit.human_name } }
    end
  end

  list
end