Files
growstuff/app/models/concerns/predict_planting.rb
T
Brenda WallaceandClaude Sonnet 5 a05df8546f Colour progress bars by how the planting is getting on
Blue while growing, green while harvesting, amber when late, red when very
late, grey when finished. The track is now neutral grey so every colour
reads against it. Planting#progress_state decides, and the garden cards,
planting progress and timeline bars use it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-20 21:08:52 +12:00

128 lines
3.9 KiB
Ruby

# frozen_string_literal: true
module PredictPlanting
extend ActiveSupport::Concern
included do
## Triggers
before_save :calculate_lifespan
def calculate_lifespan
self.lifespan = (planted_at.present? && finished_at.present? && !failed? ? finished_at - planted_at : nil)
end
# dates
def finish_predicted_at
return @finish_predicted_at if defined?(@finish_predicted_at)
@finish_predicted_at = if planted_at.blank? || failed?
nil
elsif crop.median_lifespan.present?
planted_at + crop.median_lifespan.days
elsif crop.parent.present? && crop.parent.median_lifespan.present?
planted_at + crop.parent.median_lifespan.days
end
end
# days
def expected_lifespan
return @expected_lifespan if defined?(@expected_lifespan)
@expected_lifespan = if actual_lifespan.present?
actual_lifespan
elsif crop.median_lifespan.present?
crop.median_lifespan
elsif crop.parent.present? && crop.parent.median_lifespan.present?
crop.parent.median_lifespan
end
end
def actual_lifespan
return @actual_lifespan if defined?(@actual_lifespan)
@actual_lifespan = if planted_at.present? && finished_at.present? && !failed?
(finished_at - planted_at).to_i
end
end
def age_in_days
return @age_in_days if defined?(@age_in_days)
@age_in_days = if planted_at.blank? || failed?
nil
else
known_last_day = finished_at || Time.zone.today
known_last_day = Time.zone.today if known_last_day > Time.zone.today
(known_last_day - planted_at).to_i
end
end
def percentage_grown
Rails.cache.fetch("#{cache_key_with_version}/percentage_grown", expires_in: 8.hours) do
if finished?
100
elsif !planted? || failed?
0
elsif crop.perennial || (finish_predicted_at.nil? && finished_at.nil?) # This covers future dated finished_at that hasn't occurrred yet.
nil
else
calculate_percentage_grown
end
end
end
# states
def finish_is_predicatable?
crop.annual? && planted_at.present? && finish_predicted_at.present?
end
# How the planting is getting on, for colouring its progress bar.
def progress_state
if finished?
:finished
elsif super_late?
:super_late
elsif late?
:late
elsif harvest_time?
:harvesting
else
:growing
end
end
# Planting has live more then 90 days past predicted finish
def super_late?
late? && (finish_predicted_at + 90.days) < Time.zone.today
end
def late?
crop.annual? && !finished && !failed &&
planted_at.present? &&
finish_predicted_at.present? &&
finish_predicted_at <= Time.zone.today
end
# Deactivate any plantings over time_limit that are super late in small batches.
def self.archive!(time_limit: 3.years.ago, limit: 1000)
active_plantings = Planting.annual.active.where("planted_at < ?", time_limit).order(planted_at: :asc).limit(limit)
active_plantings.each do |planting|
if planting.finish_is_predicatable? && planting.super_late?
planting.finished = true
planting.save
end
end
end
private
def calculate_percentage_grown
return 0 if age_in_days.to_i < 0
percent = (age_in_days.to_f / expected_lifespan.to_f) * 100
(percent > 100 ? 100 : percent)
end
end
end