mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-10-31 03:09:06 -04:00
Add ApplyCropJobTemplate service and for_crop scope to CropJobTemplate model
This commit is contained in:
1 parent
218c1d5c75
commit
dedd2dd52a
4 files changed
+134
No files matched your search
@@ -14,6 +14,10 @@ class CropJobTemplate < ApplicationRecord
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
# Those written for the crop itself or, if it is a variety, for the crop it
|
||||
# is a variety of.
|
||||
scope :for_crop, ->(crop) { where(crop_id: [crop.id, crop.parent_id].compact) }
|
||||
|
||||
accepts_nested_attributes_for :steps, allow_destroy: true, reject_if: :all_blank
|
||||
|
||||
def to_s
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Makes jobs for a planting from a crop job template: one for each step that
|
||||
# falls today or later. A step the planting already has a job for is not made
|
||||
# again, so applying a template twice does nothing the second time. A step
|
||||
# repeating weekly, fortnightly or monthly is made once, for its first day.
|
||||
class ApplyCropJobTemplate
|
||||
def initialize(template, planting, today: Time.zone.today)
|
||||
@template = template
|
||||
@planting = planting
|
||||
@today = today
|
||||
end
|
||||
|
||||
# The jobs made, in the order of the template's steps.
|
||||
def call
|
||||
return [] if @planting.planted_at.blank?
|
||||
|
||||
entries.filter_map { |entry| create_job(entry) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def entries
|
||||
schedule = CropJobSchedule.new(@template, planting_out_on: @planting.planted_at.to_date, today: @today)
|
||||
schedule.entries.select { |entry| entry.date && !entry.past? && applied_step_ids.exclude?(entry.step.id) }
|
||||
end
|
||||
|
||||
# Steps of the template that already have a job for this planting.
|
||||
def applied_step_ids
|
||||
@applied_step_ids ||= Job.where(jobbable: @planting, crop_job_step: @template.steps).pluck(:crop_job_step_id)
|
||||
end
|
||||
|
||||
def create_job(entry)
|
||||
Job.create!(name: entry.step.name, due_date: entry.date, owner: @planting.owner, garden: @planting.garden,
|
||||
jobbable: @planting, crop_job_step: entry.step)
|
||||
end
|
||||
end
|
||||
@@ -43,4 +43,27 @@ describe CropJobTemplate do
|
||||
expect { template.destroy }.to change(CropJobStep, :count).by(-2)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.for_crop' do
|
||||
subject(:found) { described_class.for_crop(crop) }
|
||||
|
||||
let(:parent) { create(:crop) }
|
||||
let(:crop) { create(:crop, parent:) }
|
||||
|
||||
it 'has templates written for the crop' do
|
||||
expect(found).to include(create(:crop_job_template, crop:))
|
||||
end
|
||||
|
||||
it 'has templates written for its parent' do
|
||||
expect(found).to include(create(:crop_job_template, crop: parent))
|
||||
end
|
||||
|
||||
it 'leaves out templates for other crops' do
|
||||
expect(found).not_to include(create(:crop_job_template))
|
||||
end
|
||||
|
||||
it 'leaves out templates for a sibling variety' do
|
||||
expect(found).not_to include(create(:crop_job_template, crop: create(:crop, parent:)))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplyCropJobTemplate do
|
||||
subject(:apply) { described_class.new(template, planting, today:) }
|
||||
|
||||
let(:today) { Date.new(2026, 10, 23) }
|
||||
let(:planting) { create(:planting, planted_at: Date.new(2026, 10, 20)) }
|
||||
let(:template) { create(:crop_job_template, crop: planting.crop) }
|
||||
let!(:stake) { create(:crop_job_step, crop_job_template: template, name: 'Stake', offset_amount: 2, offset_unit: 'weeks') }
|
||||
|
||||
describe '#call' do
|
||||
it 'makes a job for a step' do
|
||||
expect(apply.call.map(&:name)).to eq ['Stake']
|
||||
end
|
||||
|
||||
it 'dates the job from the planting' do
|
||||
expect(apply.call.first.due_date).to eq Date.new(2026, 11, 3)
|
||||
end
|
||||
|
||||
it 'makes the job the planting owner\'s' do
|
||||
expect(apply.call.first.owner).to eq planting.owner
|
||||
end
|
||||
|
||||
it 'makes the job about the planting' do
|
||||
expect(apply.call.first.jobbable).to eq planting
|
||||
end
|
||||
|
||||
it 'puts the job in the planting\'s garden' do
|
||||
expect(apply.call.first.garden).to eq planting.garden
|
||||
end
|
||||
|
||||
it 'records the step the job came from' do
|
||||
expect(apply.call.first.crop_job_step).to eq stake
|
||||
end
|
||||
|
||||
context 'when applied a second time' do
|
||||
before { described_class.new(template, planting, today:).call }
|
||||
|
||||
it 'makes no more jobs' do
|
||||
expect(apply.call).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a step that fell before today' do
|
||||
before { create(:crop_job_step, crop_job_template: template, name: 'Harden off', offset_amount: 1, offset_unit: 'days') }
|
||||
|
||||
it 'leaves it out' do
|
||||
expect(apply.call.map(&:name)).not_to include 'Harden off'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a step on the first harvest and no harvest date known' do
|
||||
before { create(:crop_job_step, crop_job_template: template, name: 'Pick', anchor: 'first_harvest') }
|
||||
|
||||
it 'leaves it out' do
|
||||
expect(apply.call.map(&:name)).not_to include 'Pick'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a planting that has no planting date' do
|
||||
let(:planting) { create(:planting, planted_at: nil) }
|
||||
|
||||
it 'makes no jobs' do
|
||||
expect(apply.call).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user