From ed7b83fde6c7bbf58af382401afe35f7d7d914fc Mon Sep 17 00:00:00 2001 From: Yuri Bocharov Date: Tue, 16 Jul 2024 20:47:06 -0400 Subject: [PATCH] feat: add timing metadata for case contacts (#5919) --- .../case_contacts/form_controller.rb | 3 +- app/models/case_contact.rb | 1 + app/services/case_contact_update_service.rb | 28 +++ ...716194609_add_metadata_to_case_contacts.rb | 5 + db/schema.rb | 3 +- .../case_contact_update_service_spec.rb | 203 ++++++++++++++++++ 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 app/services/case_contact_update_service.rb create mode 100644 db/migrate/20240716194609_add_metadata_to_case_contacts.rb create mode 100644 spec/services/case_contact_update_service_spec.rb diff --git a/app/controllers/case_contacts/form_controller.rb b/app/controllers/case_contacts/form_controller.rb index 2708f36ab2..d988c98bdf 100644 --- a/app/controllers/case_contacts/form_controller.rb +++ b/app/controllers/case_contacts/form_controller.rb @@ -23,7 +23,8 @@ def update params[:case_contact][:status] = step.to_s if !@case_contact.active? remove_unwanted_contact_types remove_nil_draft_ids - if @case_contact.update(case_contact_params) + + if CaseContactUpdateService.new(@case_contact).update_attrs(case_contact_params) respond_to do |format| format.html { if step == steps.last diff --git a/app/models/case_contact.rb b/app/models/case_contact.rb index 213c55d1be..37696fc9f3 100644 --- a/app/models/case_contact.rb +++ b/app/models/case_contact.rb @@ -320,6 +320,7 @@ def casa_org_any_expenses_enabled? # draft_case_ids :integer default([]), is an Array # duration_minutes :integer # medium_type :string +# metadata :jsonb # miles_driven :integer default(0), not null # notes :string # occurred_at :datetime diff --git a/app/services/case_contact_update_service.rb b/app/services/case_contact_update_service.rb new file mode 100644 index 0000000000..3b10d18542 --- /dev/null +++ b/app/services/case_contact_update_service.rb @@ -0,0 +1,28 @@ +class CaseContactUpdateService + attr_reader :case_contact + + def initialize(case_contact) + @case_contact = case_contact + end + + def update_attrs(new_attrs) + old_attrs = case_contact.as_json + + result = case_contact.update(new_attrs) + update_status_metadata(old_attrs, new_attrs) if result + + result + end + + private + + def update_status_metadata(old_attrs, new_attrs) + return if old_attrs[:status] == new_attrs[:status] + + metadata = case_contact.metadata + metadata["status"] ||= {} + metadata["status"][new_attrs[:status]] = Time.zone.now + + case_contact.update(metadata: metadata) + end +end diff --git a/db/migrate/20240716194609_add_metadata_to_case_contacts.rb b/db/migrate/20240716194609_add_metadata_to_case_contacts.rb new file mode 100644 index 0000000000..4d2372cb27 --- /dev/null +++ b/db/migrate/20240716194609_add_metadata_to_case_contacts.rb @@ -0,0 +1,5 @@ +class AddMetadataToCaseContacts < ActiveRecord::Migration[7.1] + def change + add_column :case_contacts, :metadata, :jsonb, default: {} + end +end diff --git a/db/schema.rb b/db/schema.rb index 3c91d8bac1..e4ba1f6685 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_06_22_020203) do +ActiveRecord::Schema[7.1].define(version: 2024_07_16_194609) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -203,6 +203,7 @@ t.string "status", default: "started" t.integer "draft_case_ids", default: [], array: true t.string "volunteer_address" + t.jsonb "metadata", default: {} t.index ["casa_case_id"], name: "index_case_contacts_on_casa_case_id" t.index ["creator_id"], name: "index_case_contacts_on_creator_id" t.index ["deleted_at"], name: "index_case_contacts_on_deleted_at" diff --git a/spec/services/case_contact_update_service_spec.rb b/spec/services/case_contact_update_service_spec.rb new file mode 100644 index 0000000000..b49b25a438 --- /dev/null +++ b/spec/services/case_contact_update_service_spec.rb @@ -0,0 +1,203 @@ +require "rails_helper" + +RSpec.describe CaseContactUpdateService do + let(:updater) { described_class.new(case_contact) } + let(:case_contact) { create(:case_contact) } + + let!(:now) { Time.zone.now } + let!(:one_day_ago) { 1.day.ago } + let!(:two_days_ago) { 2.days.ago } + + before { travel_to one_day_ago } + after { travel_back } + + describe "#update_attributes" do + context "case is in details status" do + let!(:case_contact) { create(:case_contact, status: "details", created_at: two_days_ago) } + + context "status is not updated" do + before { updater.update_attrs({notes: "stuff"}) } + + it { expect(case_contact.metadata).to eq({}) } + it { expect(updater.update_attrs({notes: "stuff"})).to be true } + end + + it "does not update metadata if attrs are invalid" do + result = updater.update_attrs({occurred_at: 50.years.ago}) + expect(case_contact.metadata).to eq({}) + expect(result).to be false + end + + context "gets updated to details" do + before { updater.update_attrs({status: "details"}) } + + it "updates details metadata to current date" do + date = case_contact.metadata.dig("status", "details") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("details") } + end + + context "gets updated to expenses" do + before { updater.update_attrs({status: "expenses"}) } + + it "updates expenses metadata to current date" do + date = case_contact.metadata.dig("status", "expenses") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("expenses") } + end + + context "gets updated to active" do + before { updater.update_attrs({status: "active"}) } + + it "updates active metadata to current date" do + date = case_contact.metadata.dig("status", "active") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("active") } + end + end + + context "case is in expenses status" do + let!(:case_contact) { create(:case_contact, status: "expenses", created_at: two_days_ago) } + + context "status is not updated" do + before { updater.update_attrs({notes: "stuff"}) } + + it { expect(case_contact.metadata).to eq({}) } + it { expect(updater.update_attrs({notes: "stuff"})).to be true } + end + + it "does not update metadata if attrs are invalid" do + result = updater.update_attrs({occurred_at: 50.years.ago}) + expect(case_contact.metadata).to eq({}) + expect(result).to be false + end + + context "gets updated to details" do + before { updater.update_attrs({status: "details"}) } + + it "updates details metadata to current date" do + date = case_contact.metadata.dig("status", "details") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("details") } + end + + context "gets updated to expenses" do + before { updater.update_attrs({status: "expenses"}) } + + it "updates expenses metadata to current date" do + date = case_contact.metadata.dig("status", "expenses") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("expenses") } + end + + context "gets updated to active" do + before { updater.update_attrs({status: "active"}) } + + it "updates active metadata to current date" do + date = case_contact.metadata.dig("status", "active") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("active") } + end + end + + context "case is in active status" do + let!(:case_contact) { create(:case_contact, status: "active", created_at: two_days_ago) } + + context "status is not updated" do + before { updater.update_attrs({notes: "stuff"}) } + + it { expect(case_contact.metadata).to eq({}) } + it { expect(updater.update_attrs({notes: "stuff"})).to be true } + end + + it "does not update metadata if attrs are invalid" do + result = updater.update_attrs({occurred_at: 50.years.ago}) + expect(case_contact.metadata).to eq({}) + expect(result).to be false + end + + context "gets updated to details" do + before { updater.update_attrs({status: "details"}) } + + it "updates details metadata to current date" do + date = case_contact.metadata.dig("status", "details") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("details") } + end + + context "gets updated to expenses" do + before { updater.update_attrs({status: "expenses"}) } + + it "updates expenses metadata to current date" do + date = case_contact.metadata.dig("status", "expenses") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("expenses") } + end + + context "gets updated to active" do + before { updater.update_attrs({status: "active"}) } + + it "updates active metadata to current date" do + date = case_contact.metadata.dig("status", "active") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("active") } + end + end + + context "case is in started status" do + let!(:case_contact) { create(:case_contact, status: "started", created_at: two_days_ago) } + + context "status is not updated" do + before { updater.update_attrs({notes: "stuff"}) } + + it { expect(case_contact.metadata).to eq({}) } + it { expect(updater.update_attrs({notes: "stuff"})).to be true } + end + + it "does not update metadata if attrs are invalid" do + result = updater.update_attrs({occurred_at: 50.years.ago}) + expect(case_contact.metadata).to eq({}) + expect(result).to be false + end + + context "gets updated to details" do + before { updater.update_attrs({status: "details"}) } + + it "updates details metadata to current date" do + date = case_contact.metadata.dig("status", "details") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("details") } + end + + context "gets updated to expenses" do + before { updater.update_attrs({status: "expenses"}) } + + it "updates expenses metadata to current date" do + date = case_contact.metadata.dig("status", "expenses") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("expenses") } + end + + context "gets updated to active" do + before { updater.update_attrs({status: "active"}) } + + it "updates active metadata to current date" do + date = case_contact.metadata.dig("status", "active") + expect(DateTime.parse(date)).to eq(Time.zone.now) + end + it { expect(case_contact.status).to eq("active") } + end + end + end +end