Class: ActivityProduction

Inherits:
Ekylibre::Record::Base show all
Includes:
Attachable, Customizable
Defined in:
app/models/activity_production.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: activity_productions

activity_id         :integer          not null
campaign_id         :integer
created_at          :datetime         not null
creator_id          :integer
cultivable_zone_id  :integer
custom_fields       :jsonb
id                  :integer          not null, primary key
irrigated           :boolean          default(FALSE), not null
lock_version        :integer          default(0), not null
nitrate_fixing      :boolean          default(FALSE), not null
rank_number         :integer          not null
size_indicator_name :string           not null
size_unit_name      :string
size_value          :decimal(19, 4)   not null
started_on          :date
state               :string
stopped_on          :date
support_id          :integer          not null
support_nature      :string
support_shape       :geometry({:srid=>4326, :type=>"multi_polygon"})
updated_at          :datetime         not null
updater_id          :integer
usage               :string           not null

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?

Instance Method Details

#active?Boolean


253
254
255
# File 'app/models/activity_production.rb', line 253

def active?
  activity.family.to_s != 'fallow_land'
end

#campaignsObject


262
263
264
# File 'app/models/activity_production.rb', line 262

def campaigns
  Campaign.of_activity_production(self)
end

#computed_support_nameObject


234
235
236
237
238
239
240
241
242
# File 'app/models/activity_production.rb', line 234

def computed_support_name
  list = []
  list << cultivable_zone.name if cultivable_zone
  list << activity.name
  list << campaign.name if campaign
  list << :rank.t(number: rank_number)
  list.reverse! if 'i18n.dir'.t == 'rtl'
  list.join(' ')
end

#cost(role = :input) ⇒ Object


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

def cost(role = :input)
  costs = interventions.collect do |intervention|
    intervention.cost(role)
  end
  costs.compact.sum
end

#current_campaignObject

Used for find current campaign for given production


285
286
287
# File 'app/models/activity_production.rb', line 285

def current_campaign
  Campaign.at(Time.zone.now).first
end

#current_cultivationObject


494
495
496
497
498
499
500
501
# File 'app/models/activity_production.rb', line 494

def current_cultivation
  # get the first object with variety 'plant', availables
  if cultivation = support.contents.where(type: Plant).of_variety(variant.variety).availables.reorder(:born_at).first
    return cultivation
  else
    return nil
  end
end

#current_size(options = {}) ⇒ Object

Compute quantity of a support as defined in production


508
509
510
511
512
513
# File 'app/models/activity_production.rb', line 508

def current_size(options = {})
  options[:at] ||= self.started_on ? self.started_on.to_time : Time.zone.now
  value = support.get(size_indicator_name, options)
  value = value.in(size_unit_name) unless size_unit_name.blank?
  value
end

#duplicate!(updates = {}) ⇒ Object


515
516
517
518
519
520
521
522
523
524
525
# File 'app/models/activity_production.rb', line 515

def duplicate!(updates = {})
  new_attributes = [
    :activity, :campaign, :cultivable_zone, :irrigated, :nitrate_fixing,
    :size_indicator_name, :size_unit_name, :size_value, :started_on,
    :support_nature, :support_shape, :usage
  ].each_with_object({}) do |attr, h|
    h[attr] = send(attr)
    h
  end.merge(updates)
  self.class.create!(new_attributes)
end

#estimate_yield(options = {}) ⇒ Object

call method in production for instance


482
483
484
485
486
487
488
489
490
491
492
# File 'app/models/activity_production.rb', line 482

def estimate_yield(options = {})
  # compute variety for estimate yield
  if usage == 'grain' || usage == 'seed'
    options[:variety] ||= 'grain'
  elsif usage == 'fodder' || usage == 'fiber'
    options[:variety] ||= 'grass'
  end
  # get current campaign
  options[:campaign] ||= campaign
  activity.estimate_yield_from_budget_of(options)
end

#get(*args) ⇒ Object


549
550
551
552
553
554
# File 'app/models/activity_production.rb', line 549

def get(*args)
  unless support.present?
    raise StandardError, "No support defined. Got: #{support.inspect}"
  end
  support.get(*args)
end

#grains_yield(mass_unit_name = :quintal, surface_unit_name = :hectare) ⇒ Object

Returns the yield of grain in mass per surface unit


466
467
468
469
470
471
# File 'app/models/activity_production.rb', line 466

def grains_yield(mass_unit_name = :quintal, surface_unit_name = :hectare)
  harvest_yield(:grain, procedure_category: :harvesting,
                        size_indicator_name: :net_mass,
                        size_unit_name: mass_unit_name,
                        surface_unit_name: surface_unit_name)
end

#harvest_yield(harvest_variety, options = {}) ⇒ Object

Generic method to get harvest yield


409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'app/models/activity_production.rb', line 409

def harvest_yield(harvest_variety, options = {})
  size_indicator_name = options[:size_indicator_name] || :net_mass
  ind = Nomen::Indicator.find(size_indicator_name)
  raise "Invalid indicator: #{size_indicator_name}" unless ind
  size_unit_name = options[:size_unit_name] || ind.unit
  unless Nomen::Unit.find(size_unit_name)
    raise "Invalid indicator unit: #{size_unit_name.inspect}"
  end
  surface_unit_name = options[:surface_unit_name] || :hectare
  procedure_category = options[:procedure_category] || :harvesting
  unless net_surface_area && net_surface_area.to_d > 0
    Rails.logger.warn 'No surface area. Cannot compute harvest yield'
    return nil
  end
  harvest_yield_unit_name = "#{size_unit_name}_per_#{surface_unit_name}".to_sym
  unless Nomen::Unit.find(harvest_yield_unit_name)
    raise "Harvest yield unit doesn't exist: #{harvest_yield_unit_name.inspect}"
  end
  total_quantity = 0.0.in(size_unit_name)

  target_distribution_plants = Plant.where(id: distributions.pluck(:target_id).compact)

  # get harvest_interventions firstly by distributions and secondly by inside_plants method
  harvest_interventions = Intervention.real.of_category(procedure_category).with_targets(target_distribution_plants) if target_distribution_plants.any?
  harvest_interventions ||= Intervention.real.of_category(procedure_category).with_targets(inside_plants)

  coef_area = []
  global_coef_harvest_yield = []

  if harvest_interventions.any?
    harvest_interventions.find_each do |harvest|
      harvest_working_area = []
      harvest.targets.each do |target|
        harvest_working_area << ::Charta.new_geometry(target.working_zone).area.in(:square_meter)
      end
      harvest.outputs.each do |cast|
        actor = cast.product
        next unless actor && actor.variety
        variety = Nomen::Variety.find(actor.variety)
        if variety && variety <= harvest_variety
          quantity = cast.quantity_population.in(actor.variant.send(size_indicator_name).unit)
          total_quantity += quantity.convert(size_unit_name) if quantity
        end
      end
      h = harvest_working_area.compact.sum.to_d(surface_unit_name).to_f
      if h && h > 0.0
        global_coef_harvest_yield << (h * (total_quantity.to_f / h))
        coef_area << h
      end
    end
  end

  total_weighted_average_harvest_yield = global_coef_harvest_yield.compact.sum / coef_area.compact.sum if coef_area.compact.sum.to_d != 0.0
  Measure.new(total_weighted_average_harvest_yield.to_f, harvest_yield_unit_name)
end

#harvested_atObject

Returns the started_at attribute of the intervention of nature harvesting if exist and if it's a vegetal activity


402
403
404
405
406
# File 'app/models/activity_production.rb', line 402

def harvested_at
  intervention = interventions.real.of_category(:harvesting).first
  return intervention.started_at if intervention
  nil
end

#implanted_atObject

Returns the started_at attribute of the intervention of nature sowing if exist and if it's a vegetal activity


394
395
396
397
398
# File 'app/models/activity_production.rb', line 394

def implanted_at
  intervention = interventions.real.of_category(:planting).first
  return intervention.started_at if intervention
  nil
end

#input_cost(surface_unit_name = :hectare) ⇒ Object


363
364
365
366
367
368
# File 'app/models/activity_production.rb', line 363

def input_cost(surface_unit_name = :hectare)
  if net_surface_area.to_s.to_f > 0.0
    return cost(:input) / net_surface_area.to_d(surface_unit_name).to_s.to_f
  end
  0.0
end

#inside_plantsObject

Returns all plants concerning by this activity production TODO: No plant here, a more generic method should be largely preferable


379
380
381
# File 'app/models/activity_production.rb', line 379

def inside_plants
  inside_products(Plant)
end

#inside_products(products = Product) ⇒ Object


383
384
385
386
387
388
389
390
# File 'app/models/activity_production.rb', line 383

def inside_products(products = Product)
  products = if campaign
               products.of_campaign(campaign)
             else
               products.where(born_at: started_on..(stopped_on || Time.zone.now.to_date))
           end
  products.shape_within(support_shape)
end

#interventionsObject

Returns interventions of current production


258
259
260
# File 'app/models/activity_production.rb', line 258

def interventions
  Intervention.of_activity_production(self)
end

#name(options = {}) ⇒ Object

Returns unique i18nized name for given production


536
537
538
539
540
541
542
543
544
545
546
547
# File 'app/models/activity_production.rb', line 536

def name(options = {})
  list = []
  list << activity.name unless options[:activity].is_a?(FalseClass)
  list << cultivable_zone.name if cultivable_zone
  v = Nomen::Variety.find(cultivation_variety)
  list << v.human_name if v && !(activity.name.start_with?(v.human_name) || activity.name.end_with?(v.human_name))
  # list << support.name if !options[:support].is_a?(FalseClass) && support
  list << started_on.to_date.l(format: :month) if started_on
  list << :rank.t(number: rank_number)
  list = list.reverse! if 'i18n.dir'.t == 'rtl'
  list.join(' ')
end

#nitrogen_balanceObject

TODO: for nitrogen balance but will be refactorize for any chemical components


326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'app/models/activity_production.rb', line 326

def nitrogen_balance
  # B = O - I
  balance = 0.0
  nitrogen_mass = []
  nitrogen_unity_per_hectare = nil
  if selected_manure_management_plan_zone
    # get the output O aka nitrogen_input from opened_at (in kg N / Ha )
    o = selected_manure_management_plan_zone.nitrogen_input || 0.0
    # get the nitrogen input I from opened_at to now (in kg N / Ha )
    opened_at = selected_manure_management_plan_zone.opened_at
    i = soil_enrichment_indicator_content_per_area(:nitrogen_concentration, opened_at, Time.zone.now)
    balance = o - i if i && o
  else
    balance = soil_enrichment_indicator_content_per_area(:nitrogen_concentration)
  end
  balance
end

#phosphorus_balanceObject


348
349
350
# File 'app/models/activity_production.rb', line 348

def phosphorus_balance
  soil_enrichment_indicator_content_per_area(:phosphorus_concentration)
end

#potassium_balanceObject


344
345
346
# File 'app/models/activity_production.rb', line 344

def potassium_balance
  soil_enrichment_indicator_content_per_area(:potassium_concentration)
end

#provisional_nitrogen_inputObject


352
353
354
# File 'app/models/activity_production.rb', line 352

def provisional_nitrogen_input
  0
end

#soil_enrichment_indicator_content_per_area(indicator_name, from = nil, to = nil, area_unit_name = :hectare) ⇒ Object

Returns the spreaded quantity of one chemicals components (N, P, K) per area unit Get all intervention of category 'fertilizing' and sum all indicator unity spreaded

- indicator could be (:potassium_concentration, :nitrogen_concentration, :phosphorus_concentration)
- area_unit could be (:hectare, :square_meter)
- from and to used to select intervention

301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'app/models/activity_production.rb', line 301

def soil_enrichment_indicator_content_per_area(indicator_name, from = nil, to = nil, area_unit_name = :hectare)
  balance = []
  procedure_category = :fertilizing
  interventions = if from && to
                    self.interventions.real.of_category(procedure_category).between(from, to)
                  else
                    self.interventions.real.of_category(procedure_category)
                  end
  interventions.each do |intervention|
    intervention.inputs.each do |input|
      # m = net_mass of the input at intervention time
      # n = indicator (in %) of the input at intervention time
      m = (input.actor ? input.actor.net_mass(input).to_d(:kilogram) : 0.0)
      # TODO: for method phosphorus_concentration(input)
      n = (input.actor ? input.actor.send(indicator_name).to_d(:unity) : 0.0)
      balance << m * n
    end
  end
  # if net_surface_area, make the division
  area = net_surface_area.to_d(area_unit_name)
  indicator_unity_per_hectare = balance.compact.sum / area if area != 0
  indicator_unity_per_hectare
end

#started_on_for(campaign) ⇒ Object


266
267
268
269
270
271
272
273
274
275
# File 'app/models/activity_production.rb', line 266

def started_on_for(campaign)
  return self.started_on if annual?
  on = begin
         Date.civil(campaign.harvest_year, self.started_on.month, self.started_on.day)
       rescue
         Date.civil(campaign.harvest_year, self.started_on.month, self.started_on.day - 1)
       end
  on -= 1.year if at_cycle_end?
  on
end

#stopped_on_for(campaign) ⇒ Object


277
278
279
280
281
282
# File 'app/models/activity_production.rb', line 277

def stopped_on_for(campaign)
  return stopped_on if annual?
  on = Date.civil(campaign.harvest_year, self.started_on.month, self.started_on.day) - 1
  on += 1.year if at_cycle_start?
  on
end

#time_cost(surface_unit_name = :hectare) ⇒ Object


370
371
372
373
374
375
# File 'app/models/activity_production.rb', line 370

def time_cost(surface_unit_name = :hectare)
  if net_surface_area.to_s.to_f > 0.0
    return cost(:doer) / net_surface_area.to_d(surface_unit_name).to_s.to_f
  end
  0.0
end

#tool_cost(surface_unit_name = :hectare) ⇒ Object


356
357
358
359
360
361
# File 'app/models/activity_production.rb', line 356

def tool_cost(surface_unit_name = :hectare)
  if net_surface_area.to_s.to_f > 0.0
    return cost(:tool) / net_surface_area.to_d(surface_unit_name).to_s.to_f
  end
  0.0
end

#unified_size_unitObject


503
504
505
# File 'app/models/activity_production.rb', line 503

def unified_size_unit
  size_unit_name.blank? ? :unity : size_unit_name
end

#update_namesObject


244
245
246
247
248
249
250
251
# File 'app/models/activity_production.rb', line 244

def update_names
  if support
    new_support_name = computed_support_name
    if support.name != new_support_name
      support.update_column(:name, new_support_name)
    end
  end
end

#vine_yield(volume_unit_name = :hectoliter, surface_unit_name = :hectare) ⇒ Object

Returns the yield of grape in volume per surface unit


474
475
476
477
478
479
# File 'app/models/activity_production.rb', line 474

def vine_yield(volume_unit_name = :hectoliter, surface_unit_name = :hectare)
  harvest_yield(:grape, procedure_category: :harvesting,
                        size_indicator_name: :net_volume,
                        size_unit_name: volume_unit_name,
                        surface_unit_name: surface_unit_name)
end

#work_nameObject

LABEL METHODS ##


531
532
533
# File 'app/models/activity_production.rb', line 531

def work_name
  "#{support_work_number} - #{net_surface_area.convert(:hectare).round(2)}"
end