Skip to content

Commit

Permalink
Fed tax brackets (#6)
Browse files Browse the repository at this point in the history
* Add Federal Tax Rates and test

* Lint Federal Tax Rates

* Add method and poro for calculating taxes on incomes

* Add tax brackets and taxed incomes to dashboard

* Display taxed incomes with turbo

* Update income form select box to display income type

* Lint

* Update test suit

* Lint
  • Loading branch information
neb417 authored Nov 7, 2023
1 parent 7e03276 commit 0ef778d
Show file tree
Hide file tree
Showing 36 changed files with 781 additions and 15 deletions.
5 changes: 4 additions & 1 deletion app/controllers/dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
class DashboardController < ApplicationController
def index
@incomes = Income.all
@incomes = Income.order_by_type
@fixed_expenses = FixedExpense.get_ordered
@totals = FixedExpense.total_costs
@federal_tax_brackets = FederalTaxBracket.order_by_range
@salary_taxed = Income.tax_on_income(income_type: "Salary")
@hourly_taxed = Income.tax_on_income(income_type: "Hourly")
end
end
74 changes: 74 additions & 0 deletions app/controllers/federal_tax_brackets_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
class FederalTaxBracketsController < ApplicationController
before_action :set_federal_tax_bracket, only: %i[show edit update destroy]

# GET /federal_tax_brackets or /federal_tax_brackets.json
def index
@federal_tax_brackets = FederalTaxBracket.order_by_range
end

# GET /federal_tax_brackets/1 or /federal_tax_brackets/1.json
def show
end

# GET /federal_tax_brackets/new
def new
@federal_tax_bracket = FederalTaxBracket.new
end

# GET /federal_tax_brackets/1/edit
def edit
end

# POST /federal_tax_brackets or /federal_tax_brackets.json
def create
@federal_tax_bracket = FederalTaxBracket.new(federal_tax_bracket_params)
@federal_tax_bracket.update_rate(rate: params[:rate])

respond_to do |format|
if @federal_tax_bracket.save
format.html { redirect_to federal_tax_bracket_url(@federal_tax_bracket), notice: "Federal tax bracket was successfully created." }
format.json { render :show, status: :created, location: @federal_tax_bracket }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @federal_tax_bracket.errors, status: :unprocessable_entity }
end
end
end

# PATCH/PUT /federal_tax_brackets/1 or /federal_tax_brackets/1.json
def update
respond_to do |format|
if @federal_tax_bracket.update(federal_tax_bracket_params)
format.html { redirect_to federal_tax_bracket_url(@federal_tax_bracket), notice: "Federal tax bracket was successfully updated." }
# format.json { render :show, status: :ok, location: @federal_tax_bracket }
format.turbo_stream { render turbo_stream: turbo_stream.update(@federal_tax_bracket) }

else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @federal_tax_bracket.errors, status: :unprocessable_entity }
end
end
end

# DELETE /federal_tax_brackets/1 or /federal_tax_brackets/1.json
def destroy
@federal_tax_bracket.destroy

respond_to do |format|
format.html { redirect_to federal_tax_brackets_url, notice: "Federal tax bracket was successfully destroyed." }
format.json { head :no_content }
end
end

private

# Use callbacks to share common setup or constraints between actions.
def set_federal_tax_bracket
@federal_tax_bracket = FederalTaxBracket.find(params[:id])
end

# Only allow a list of trusted parameters through.
def federal_tax_bracket_params
params.require(:federal_tax_bracket).permit(:tier, :bottom_range, :top_range, :rate, :cumulative)
end
end
4 changes: 3 additions & 1 deletion app/controllers/incomes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ def create
def update
respond_to do |format|
if @income.update_from_dashboard(params: params)
@salary_taxed = Income.tax_on_income(income_type: "Salary")
@hourly_taxed = Income.tax_on_income(income_type: "Hourly")
format.html { redirect_to root_path, notice: "Income was successfully updated." }
format.json { render :show, status: :ok, location: @income }
format.turbo_stream
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @income.errors, status: :unprocessable_entity }
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/federal_tax_brackets_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module FederalTaxBracketsHelper
end
31 changes: 31 additions & 0 deletions app/models/federal_tax_bracket.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# == Schema Information
#
# Table name: federal_tax_brackets
#
# id :bigint not null, primary key
# bottom_range_cents :integer default(0), not null
# bottom_range_currency :string default("USD"), not null
# cumulative_cents :integer default(0), not null
# cumulative_currency :string default("USD"), not null
# rate :float
# tier :string
# top_range_cents :integer default(0), not null
# top_range_currency :string default("USD"), not null
# created_at :datetime not null
# updated_at :datetime not null
#
class FederalTaxBracket < ApplicationRecord
monetize :bottom_range_cents
monetize :top_range_cents
monetize :cumulative_cents

def self.order_by_range
all.order(:bottom_range_cents)
end

def update_rate(rate:)
update(
rate: rate / 100
)
end
end
19 changes: 19 additions & 0 deletions app/models/income.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,23 @@ def update_from_dashboard(params:)
false
end
end

def self.order_by_type
Income.all.order(income_type: :desc)
end

def is_hourly?
income_type == "Hourly"
end

def is_salary?
income_type == "Salary"
end

def self.tax_on_income(income_type:)
income = Income.find_by(income_type: income_type)
taxable_income = IncomeTaxCalculator.new(income: income)
taxable_income.calculate_taxes
taxable_income
end
end
52 changes: 52 additions & 0 deletions app/poros/income_tax_calculator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
class IncomeTaxCalculator
attr_reader :income,
:annual_income,
:federal_tax,
:net_after_fed_tax,
:state_tax,
:total_net_income,
:bi_weekly_net_income

def initialize(income:)
@income = income
@annual_income = income.weekly_income_cents * 52
@federal_tax = nil
@net_after_fed_tax = nil
@state_tax = nil
@total_net_income = nil
@bi_weekly_net_income = nil
end

def calculate_taxes
@federal_tax = calculate_fed_tax
@net_after_fed_tax = calculate_net_after_fed_tax
@state_tax = calculate_state_tax
@total_net_income = calculate_total_net_income
@bi_weekly_net_income = calculate_bi_weekly_income
end

private

def calculate_fed_tax
bracket = FederalTaxBracket.where("bottom_range_cents <= ?", @annual_income).order(:bottom_range_cents).last
rated = bracket.rate * @annual_income
Money.new(rated + bracket.cumulative_cents, "USD")
end

def calculate_state_tax
state_tax = (@net_after_fed_tax.fractional * 0.0463).to_i
Money.new(state_tax, "USD")
end

def calculate_net_after_fed_tax
Money.new(@annual_income - @federal_tax.fractional, "USD")
end

def calculate_total_net_income
Money.new(@net_after_fed_tax.fractional - @state_tax.fractional, "USD")
end

def calculate_bi_weekly_income
Money.new(@total_net_income.fractional / 26)
end
end
10 changes: 10 additions & 0 deletions app/views/dashboard/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
<%= turbo_frame_tag :incomes do %>
<%= render "incomes/index" %>
<% end %>

<%= turbo_frame_tag "taxed_incomes" do %>
<%= render partial: "shared/taxed_incomes", locals: { salary_taxed: @salary_taxed, hourly_taxed: @hourly_taxed} %>
<% end %>
</div>
</div>

Expand All @@ -41,5 +45,11 @@
</div>
</div>

<div class="bg-white shadow">
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
<%= render "federal_tax_brackets/index" %>
</div>
</div>

</main>
</div>
9 changes: 9 additions & 0 deletions app/views/federal_tax_brackets/_federal_tax_bracket.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%= turbo_frame_tag dom_id(federal_tax_bracket) do %>
<div class="grid grid-cols-6">
<% attributes = federal_tax_bracket.tier, humanized_money_with_symbol(federal_tax_bracket.bottom_range), humanized_money_with_symbol(federal_tax_bracket.top_range), number_to_percentage(federal_tax_bracket.rate * 100, precision: 1), humanized_money_with_symbol(federal_tax_bracket.cumulative) %>
<% attributes.each do |attribute| %>
<div class="px-5 py-1"><%= attribute %></div>
<% end %>
<div class="px-5 py-1"><%= link_to 'Edit', edit_federal_tax_bracket_path(federal_tax_bracket), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %></div>
</div>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
json.extract! federal_tax_bracket, :id, :tier, :bottom_range, :top_range, :rate, :cumulative, :created_at, :updated_at
json.url federal_tax_bracket_url(federal_tax_bracket, format: :json)
46 changes: 46 additions & 0 deletions app/views/federal_tax_brackets/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<%= form_with(model: federal_tax_bracket, class: "contents") do |form| %>
<% if federal_tax_bracket.errors.any? %>
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3">
<h2><%= pluralize(federal_tax_bracket.errors.count, "error") %> prohibited this federal_tax_bracket from being saved:</h2>

<ul>
<% federal_tax_bracket.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="flex-container">
<div class="my-5">
<%= form.label :tier %>
<%= form.text_field :tier, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
</div>

<div class="my-5">
<%= form.label :bottom_range %>
<%= form.number_field :bottom_range, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
</div>

<div class="my-5">
<%= form.label :top_range %>
<%= form.number_field :top_range, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
</div>

<div class="my-5">
<%= form.label :rate %>
<%= form.text_field :rate, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full" %>
</div>

<div class="my-5">
<%= form.label :cumulative %>
<%= form.number_field :cumulative, class: "block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full", step: :any %>
</div>

<div class="inline">
<%= form.submit class: "rounded-lg py-1 px-4 bg-gray-100 inline-block font-medium" %>
</div>
<div>
<%= link_to "Cancel", fixed_expenses_path, class: "rounded-lg py-1 px-4 bg-gray-100 inline-block font-medium" %>
</div>
</div>
<% end %>
9 changes: 9 additions & 0 deletions app/views/federal_tax_brackets/_index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="grid grid-cols-6">
<% tax_headings = %w[Tier From To Rate Cumulative] %>
<% tax_headings.each do |tax_heading| %>
<div class="px-5"><strong><%= tax_heading %></strong></div>
<% end %>
</div>
<%= turbo_frame_tag :federal_tax_brackets do %>
<%= render @federal_tax_brackets %>
<% end %>
10 changes: 10 additions & 0 deletions app/views/federal_tax_brackets/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div class="mx-auto md:w-2/3 w-full">
<h1 class="font-bold text-4xl">Editing federal tax bracket</h1>

<%= turbo_frame_tag @federal_tax_bracket do %>
<%= render "form", federal_tax_bracket: @federal_tax_bracket %>
<% end %>

<%= link_to "Show this federal tax bracket", @federal_tax_bracket, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
<%= link_to "Back to federal tax brackets", federal_tax_brackets_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>
14 changes: 14 additions & 0 deletions app/views/federal_tax_brackets/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="w-full">
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>

<div class="flex justify-between items-center">
<h1 class="font-bold text-4xl">Federal Tax Brackets</h1>
<%= link_to 'New federal tax bracket', new_federal_tax_bracket_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
</div>

<div class="min-w-full">
<%= render "federal_tax_brackets/index" %>
</div>
</div>
1 change: 1 addition & 0 deletions app/views/federal_tax_brackets/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
json.array! @federal_tax_brackets, partial: "federal_tax_brackets/federal_tax_bracket", as: :federal_tax_bracket
7 changes: 7 additions & 0 deletions app/views/federal_tax_brackets/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="mx-auto md:w-2/3 w-full">
<h1 class="font-bold text-4xl">New federal tax bracket</h1>

<%= render "form", federal_tax_bracket: @federal_tax_bracket %>

<%= link_to 'Back to federal tax brackets', federal_tax_brackets_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>
15 changes: 15 additions & 0 deletions app/views/federal_tax_brackets/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="mx-auto md:w-2/3 w-full flex">
<div class="mx-auto">
<% if notice.present? %>
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
<% end %>

<%= render @federal_tax_bracket %>

<%= link_to 'Edit this federal_tax_bracket', edit_federal_tax_bracket_path(@federal_tax_bracket), class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
<div class="inline-block ml-2">
<%= button_to 'Destroy this federal_tax_bracket', federal_tax_bracket_path(@federal_tax_bracket), method: :delete, class: "mt-2 rounded-lg py-3 px-5 bg-gray-100 font-medium" %>
</div>
<%= link_to 'Back to federal_tax_brackets', federal_tax_brackets_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
</div>
</div>
1 change: 1 addition & 0 deletions app/views/federal_tax_brackets/show.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
json.partial! "federal_tax_brackets/federal_tax_bracket", federal_tax_bracket: @federal_tax_bracket
8 changes: 7 additions & 1 deletion app/views/incomes/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
<div class="flex-container">
<div class="my-5 px-5">
<%= form.label :income_type %><br>
<%= form.select :income_type, [["Hourly", "HR"], ["Salary", "SA"]], { }, {class: "block shadow rounded-md border border-gray-200 px-3 py-2 mt-2 w-full"} %>
<% if @income.income_type.nil? %>
<%= form.select :income_type, [["Salary", "SA"], ["Hourly", "HR"]], {}, {class: "block shadow rounded-md border border-gray-200 px-3 py-2 mt-2 w-full"} %>
<% elsif @income.is_salary? %>
<%= form.select :income_type, [["Salary", "SA"]], {}, {class: "block shadow rounded-md border border-gray-200 px-3 py-2 mt-2 w-full"} %>
<% else %>
<%= form.select :income_type, [["Hourly", "HR"]], {}, {class: "block shadow rounded-md border border-gray-200 px-3 py-2 mt-2 w-full"} %>
<% end %>
</div>

<div class="my-5 px-5">
Expand Down
5 changes: 5 additions & 0 deletions app/views/incomes/update.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%= turbo_stream.update @income %>

<%= turbo_stream.replace "taxed_incomes" do %>
<%= render partial: "shared/taxed_incomes", locals: { salary_taxed: @salary_taxed, hourly_taxed: @hourly_taxed } %>
<% end %>
4 changes: 4 additions & 0 deletions app/views/shared/_taxed_income.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="px-5"><%= humanized_money_with_symbol(taxed_income.federal_tax) %></div>
<div class="px-5"><%= humanized_money_with_symbol(taxed_income.state_tax) %></div>
<div class="px-5"><%= humanized_money_with_symbol(taxed_income.total_net_income) %></div>
<div class="px-5"><%= humanized_money_with_symbol(taxed_income.bi_weekly_net_income) %></div>
Loading

0 comments on commit 0ef778d

Please sign in to comment.