From 359bceb58e6ec4af8525f7e1d5d2e9585d8e4864 Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Fri, 23 Aug 2024 09:33:42 -0400 Subject: [PATCH] Vehicle view (#1117) --- app/controllers/vehicles_controller.rb | 42 +++++++++++ app/helpers/accounts_helper.rb | 6 +- app/helpers/vehicles_helper.rb | 2 + app/models/measurement.rb | 2 +- app/models/property.rb | 4 -- app/models/vehicle.rb | 19 +++++ .../accounts/accountables/_vehicle.html.erb | 23 ++++++ .../accountables/vehicle/_overview.html.erb | 49 +++++++++++++ config/locales/views/accounts/en.yml | 18 +++++ config/locales/views/vehicles/en.yml | 7 ++ config/routes.rb | 1 + .../20240823125526_add_details_to_vehicle.rb | 9 +++ db/schema.rb | 7 +- test/controllers/vehicles_controller_test.rb | 71 +++++++++++++++++++ test/system/accounts_test.rb | 7 +- 15 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 app/controllers/vehicles_controller.rb create mode 100644 app/helpers/vehicles_helper.rb create mode 100644 app/views/accounts/accountables/vehicle/_overview.html.erb create mode 100644 config/locales/views/vehicles/en.yml create mode 100644 db/migrate/20240823125526_add_details_to_vehicle.rb create mode 100644 test/controllers/vehicles_controller_test.rb diff --git a/app/controllers/vehicles_controller.rb b/app/controllers/vehicles_controller.rb new file mode 100644 index 00000000..b2d81ed6 --- /dev/null +++ b/app/controllers/vehicles_controller.rb @@ -0,0 +1,42 @@ +class VehiclesController < ApplicationController + before_action :set_account, only: :update + + def create + account = Current.family + .accounts + .create_with_optional_start_balance! \ + attributes: account_params.except(:start_date, :start_balance), + start_date: account_params[:start_date], + start_balance: account_params[:start_balance] + + account.sync_later + redirect_to account, notice: t(".success") + end + + def update + @account.update!(account_params) + @account.sync_later + redirect_to @account, notice: t(".success") + end + + private + + def set_account + @account = Current.family.accounts.find(params[:id]) + end + + def account_params + params.require(:account) + .permit( + :name, :balance, :start_date, :start_balance, :currency, :accountable_type, + accountable_attributes: [ + :id, + :make, + :model, + :year, + :mileage_value, + :mileage_unit + ] + ) + end +end diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb index c26fde98..33d9cfcb 100644 --- a/app/helpers/accounts_helper.rb +++ b/app/helpers/accounts_helper.rb @@ -29,6 +29,8 @@ module AccountsHelper case account.accountable_type when "Property" properties_path + when "Vehicle" + vehicles_path else accounts_path end @@ -38,6 +40,8 @@ module AccountsHelper case account.accountable_type when "Property" property_path(account) + when "Vehicle" + vehicle_path(account) else account_path(account) end @@ -51,7 +55,7 @@ module AccountsHelper transactions_tab = { key: "transactions", label: t("accounts.show.transactions"), path: account_path(account, tab: "transactions"), route: account_transactions_path(account) } trades_tab = { key: "trades", label: t("accounts.show.trades"), path: account_path(account, tab: "trades"), route: account_trades_path(account) } - return [ overview_tab, value_tab ] if account.property? + return [ overview_tab, value_tab ] if account.property? || account.vehicle? return [ holdings_tab, cash_tab, trades_tab ] if account.investment? [ value_tab, transactions_tab ] diff --git a/app/helpers/vehicles_helper.rb b/app/helpers/vehicles_helper.rb new file mode 100644 index 00000000..f8e7abd9 --- /dev/null +++ b/app/helpers/vehicles_helper.rb @@ -0,0 +1,2 @@ +module VehiclesHelper +end diff --git a/app/models/measurement.rb b/app/models/measurement.rb index 56b3ec43..ef57086c 100644 --- a/app/models/measurement.rb +++ b/app/models/measurement.rb @@ -3,7 +3,7 @@ class Measurement attr_reader :value, :unit - VALID_UNITS = %w[sqft sqm] + VALID_UNITS = %w[sqft sqm mi km] validates :unit, inclusion: { in: VALID_UNITS } validates :value, presence: true diff --git a/app/models/property.rb b/app/models/property.rb index d5af127b..a23519ed 100644 --- a/app/models/property.rb +++ b/app/models/property.rb @@ -15,10 +15,6 @@ class Property < ApplicationRecord first_valuation_amount end - def equity_value - account.balance_money - end - def trend TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount) end diff --git a/app/models/vehicle.rb b/app/models/vehicle.rb index 13d20150..2ab34d7b 100644 --- a/app/models/vehicle.rb +++ b/app/models/vehicle.rb @@ -1,3 +1,22 @@ class Vehicle < ApplicationRecord include Accountable + + attribute :mileage_unit, :string, default: "mi" + + def mileage + Measurement.new(mileage_value, mileage_unit) if mileage_value.present? + end + + def purchase_price + first_valuation_amount + end + + def trend + TimeSeries::Trend.new(current: account.balance_money, previous: first_valuation_amount) + end + + private + def first_valuation_amount + account.entries.account_valuations.order(:date).first&.amount_money || account.balance_money + end end diff --git a/app/views/accounts/accountables/_vehicle.html.erb b/app/views/accounts/accountables/_vehicle.html.erb index e69de29b..e6fe2443 100644 --- a/app/views/accounts/accountables/_vehicle.html.erb +++ b/app/views/accounts/accountables/_vehicle.html.erb @@ -0,0 +1,23 @@ +<%# locals: (f:) %> + +
+
+ +
+ <%= f.fields_for :accountable do |vehicle_form| %> + +
+ <%= vehicle_form.text_field :make, label: t(".make"), placeholder: t(".make_placeholder") %> + <%= vehicle_form.text_field :model, label: t(".model"), placeholder: t(".model_placeholder") %> +
+ +
+ <%= vehicle_form.text_field :year, label: t(".year"), placeholder: t(".year_placeholder") %> + <%= vehicle_form.text_field :mileage_value, label: t(".mileage"), placeholder: t(".mileage_placeholder") %> + <%= vehicle_form.select :mileage_unit, + [["Miles", "mi"], ["Kilometers", "km"]], + { label: t(".mileage_unit") } %> +
+ <% end %> +
+
diff --git a/app/views/accounts/accountables/vehicle/_overview.html.erb b/app/views/accounts/accountables/vehicle/_overview.html.erb new file mode 100644 index 00000000..c371a7bb --- /dev/null +++ b/app/views/accounts/accountables/vehicle/_overview.html.erb @@ -0,0 +1,49 @@ +<%# locals: (account:) %> + +
+
+

<%= t(".make_model") %>

+

+ <%= [account.vehicle.make, account.vehicle.model].compact.join(" ").presence || t(".unknown") %> +

+
+ +
+

<%= t(".year") %>

+

+ <%= account.vehicle.year || t(".unknown") %> +

+
+ +
+

<%= t(".mileage") %>

+

+ <%= account.vehicle.mileage || t(".unknown") %> +

+
+ +
+

<%= t(".purchase_price") %>

+

+ <%= format_money account.vehicle.purchase_price %> +

+
+ +
+

<%= t(".current_price") %>

+

+ <%= format_money account.balance_money %> +

+
+ +
+

<%= t(".trend") %>

+
+

+ <%= account.vehicle.trend.value %> +

+ +

(<%= account.vehicle.trend.percent %>%)

+
+
+
diff --git a/config/locales/views/accounts/en.yml b/config/locales/views/accounts/en.yml index eb4bdba1..7dcc5a9a 100644 --- a/config/locales/views/accounts/en.yml +++ b/config/locales/views/accounts/en.yml @@ -19,6 +19,24 @@ en: postal_code: Postal code (optional) state: State year_built: Year built (optional) + vehicle: + make: Make + make_placeholder: Toyota + mileage: Mileage + mileage_placeholder: '15000' + mileage_unit: Unit + model: Model + model_placeholder: Camry + overview: + current_price: Current Price + make_model: Make & Model + mileage: Mileage + purchase_price: Purchase Price + trend: Trend + unknown: Unknown + year: Year + year: Year + year_placeholder: '2023' create: success: New account created successfully destroy: diff --git a/config/locales/views/vehicles/en.yml b/config/locales/views/vehicles/en.yml new file mode 100644 index 00000000..73e0b8e5 --- /dev/null +++ b/config/locales/views/vehicles/en.yml @@ -0,0 +1,7 @@ +--- +en: + vehicles: + create: + success: Vehicle created successfully + update: + success: Vehicle updated successfully diff --git a/config/routes.rb b/config/routes.rb index 25b92740..4ffc2e66 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -90,6 +90,7 @@ Rails.application.routes.draw do end resources :properties, only: %i[ create update ] + resources :vehicles, only: %i[ create update ] resources :transactions, only: %i[ index new create ] do collection do diff --git a/db/migrate/20240823125526_add_details_to_vehicle.rb b/db/migrate/20240823125526_add_details_to_vehicle.rb new file mode 100644 index 00000000..6ab6dd6f --- /dev/null +++ b/db/migrate/20240823125526_add_details_to_vehicle.rb @@ -0,0 +1,9 @@ +class AddDetailsToVehicle < ActiveRecord::Migration[7.2] + def change + add_column :vehicles, :year, :integer + add_column :vehicles, :mileage_value, :integer + add_column :vehicles, :mileage_unit, :string + add_column :vehicles, :make, :string + add_column :vehicles, :model, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index cb2fe1ff..7833d7ad 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.2].define(version: 2024_08_22_180845) do +ActiveRecord::Schema[7.2].define(version: 2024_08_23_125526) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -441,6 +441,11 @@ ActiveRecord::Schema[7.2].define(version: 2024_08_22_180845) do create_table "vehicles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "year" + t.integer "mileage_value" + t.string "mileage_unit" + t.string "make" + t.string "model" end add_foreign_key "account_balances", "accounts", on_delete: :cascade diff --git a/test/controllers/vehicles_controller_test.rb b/test/controllers/vehicles_controller_test.rb new file mode 100644 index 00000000..2aecb42f --- /dev/null +++ b/test/controllers/vehicles_controller_test.rb @@ -0,0 +1,71 @@ +require "test_helper" + +class VehiclesControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in @user = users(:family_admin) + @account = accounts(:vehicle) + end + + test "creates vehicle" do + assert_difference -> { Account.count } => 1, + -> { Vehicle.count } => 1, + -> { Account::Valuation.count } => 2, + -> { Account::Entry.count } => 2 do + post vehicles_path, params: { + account: { + name: "Vehicle", + balance: 30000, + currency: "USD", + accountable_type: "Vehicle", + start_date: 1.year.ago.to_date, + start_balance: 35000, + accountable_attributes: { + make: "Toyota", + model: "Camry", + year: 2020, + mileage_value: 15000, + mileage_unit: "mi" + } + } + } + end + + created_account = Account.order(:created_at).last + + assert_equal "Toyota", created_account.vehicle.make + assert_equal "Camry", created_account.vehicle.model + assert_equal 2020, created_account.vehicle.year + assert_equal 15000, created_account.vehicle.mileage_value + assert_equal "mi", created_account.vehicle.mileage_unit + + assert_redirected_to account_path(created_account) + assert_equal "Vehicle created successfully", flash[:notice] + assert_enqueued_with(job: AccountSyncJob) + end + + test "updates vehicle" do + assert_no_difference [ "Account.count", "Vehicle.count", "Account::Valuation.count", "Account::Entry.count" ] do + patch vehicle_path(@account), params: { + account: { + name: "Updated Vehicle", + balance: 28000, + currency: "USD", + accountable_type: "Vehicle", + accountable_attributes: { + id: @account.accountable_id, + make: "Honda", + model: "Accord", + year: 2021, + mileage_value: 20000, + mileage_unit: "mi", + purchase_price: 32000 + } + } + } + end + + assert_redirected_to account_path(@account) + assert_equal "Vehicle updated successfully", flash[:notice] + assert_enqueued_with(job: AccountSyncJob) + end +end diff --git a/test/system/accounts_test.rb b/test/system/accounts_test.rb index 3873e784..57f4268e 100644 --- a/test/system/accounts_test.rb +++ b/test/system/accounts_test.rb @@ -34,7 +34,12 @@ class AccountsTest < ApplicationSystemTestCase end test "can create vehicle account" do - assert_account_created("Vehicle") + assert_account_created "Vehicle" do + fill_in "Make", with: "Toyota" + fill_in "Model", with: "Camry" + fill_in "Year", with: "2020" + fill_in "Mileage", with: "30000" + end end test "can create other asset account" do