Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show bank account details on invoices #1908

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class InternalApi::V1::Invoices::ViewController < InternalApi::V1::ApplicationCo
def show
invoice.viewed! if invoice.sent?
Invoices::EventTrackerService.new("view", invoice, params).process
render :show, locals: { invoice:, stripe_connected_account: }
render :show, locals: { invoice:, stripe_connected_account:, bank_account: invoice.company.bank_account }
end

private
Expand Down
23 changes: 19 additions & 4 deletions app/controllers/internal_api/v1/payment_settings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ class InternalApi::V1::PaymentSettingsController < InternalApi::V1::ApplicationC
after_action :save_stripe_settings, only: :index

def index
authorize :index, policy_class: PaymentSettingsPolicy
authorize current_company, policy_class: PaymentSettingsPolicy

render :index, locals: { stripe_connected_account: }
render :index, locals: { stripe_connected_account:, bank_account: current_company.bank_account }
end

def connect_stripe
authorize :connect_stripe, policy_class: PaymentSettingsPolicy
authorize current_company, policy_class: PaymentSettingsPolicy

StripeConnectedAccount.create!({ company: current_company }) if stripe_connected_account.nil?

render :connect_stripe, locals: { stripe_connected_account: }
end

def destroy
authorize :destroy, policy_class: PaymentSettingsPolicy
authorize current_company, policy_class: PaymentSettingsPolicy

if stripe_connected_account.destroy
render json: { notice: "Stripe connection disconnected" }, status: :ok
Expand All @@ -27,6 +27,17 @@ def destroy
end
end

def update_bank_account
authorize current_company, policy_class: PaymentSettingsPolicy

@bank_account = current_company.bank_account || current_company.build_bank_account
if @bank_account.update(bank_account_params)
render json: { notice: "Bank account details updated successfully" }, status: :ok
else
render json: { error: @bank_account.errors.full_messages }, status: :unprocessable_entity
end
end

private

def stripe_connected_account
Expand All @@ -36,4 +47,8 @@ def stripe_connected_account
def save_stripe_settings
PaymentProviders::CreateStripeProviderService.process(current_company)
end

def bank_account_params
params.require(:bank_account).permit(:routing_number, :account_number, :account_type, :bank_name)
end
end
4 changes: 2 additions & 2 deletions app/controllers/payment_settings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

class PaymentSettingsController < ApplicationController
def index
authorize :index, policy_class: PaymentSettingsPolicy
authorize current_company, policy_class: PaymentSettingsPolicy
end

def refresh_stripe_connect
authorize :refresh_stripe_connect, policy_class: PaymentSettingsPolicy
authorize current_company, policy_class: PaymentSettingsPolicy

redirect_to stripe_connected_account.url, allow_other_host: true
end
Expand Down
10 changes: 10 additions & 0 deletions app/models/bank_account.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class BankAccount < ApplicationRecord
belongs_to :company

validates :routing_number, presence: true
validates :account_number, presence: true
validates :account_type, presence: true, inclusion: { in: %w[checking savings] }
validates :bank_name, presence: true
end
1 change: 1 addition & 0 deletions app/models/company.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Company < ApplicationRecord
has_many :invoices
has_many :payments, through: :invoices
has_one :stripe_connected_account, dependent: :destroy
has_one :bank_account, dependent: :destroy
has_many :payments_providers, dependent: :destroy
has_many :addresses, as: :addressable, dependent: :destroy
has_many :devices, dependent: :destroy
Expand Down
21 changes: 17 additions & 4 deletions app/policies/payment_settings_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,31 @@

class PaymentSettingsPolicy < ApplicationPolicy
def index?
user_owner_role? || user_admin_role?
has_owner_or_admin_role?(record)
end

def connect_stripe?
user_owner_role? || user_admin_role?
has_owner_or_admin_role?(record)
end

def destroy?
user_owner_role? || user_admin_role?
has_owner_or_admin_role?(record)
end

def refresh_stripe_connect?
user_owner_role? || user_admin_role?
has_owner_or_admin_role?(record)
end

def update_bank_account?
has_owner_or_admin_role?(record)
end

private

def has_owner_or_admin_role?(company)
user.has_any_role?(
{ name: :admin, resource: company },
{ name: :owner, resource: company }
)
end
end
4 changes: 3 additions & 1 deletion app/services/invoice_payment/pdf_generation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def initialize(invoice, company_logo, root_url, filepath = nil)
@base_currency = invoice.company.base_currency
@root_url = root_url
@filepath = filepath
@bank_account = invoice.company.bank_account
end

def process
Expand All @@ -24,7 +25,8 @@ def process
client: @invoice.client,
invoice_line_items: invoice_data[:invoice_line_items],
sub_total: format_currency(invoice_data[:sub_total]),
total: format_currency(invoice_data[:total])
total: format_currency(invoice_data[:total]),
bank_account: @bank_account
}

Pdf::HtmlGenerator.new(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

json.bank_name bank_account&.bank_name
json.account_type bank_account&.account_type&.capitalize
json.routing_number bank_account&.routing_number
json.account_number bank_account&.account_number
3 changes: 3 additions & 0 deletions app/views/internal_api/v1/invoices/view/show.json.jbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ end
json.client do
json.partial! "internal_api/v1/partial/client", locals: { client: invoice.client }
end
json.bank_account do
json.partial! "internal_api/v1/invoices/bank_account", bank_account:
end
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ json.providers do
json.paypal do
json.connected false
end
json.bank_account do
json.partial! "internal_api/v1/invoices/bank_account", bank_account:
end
end
9 changes: 9 additions & 0 deletions app/views/pdfs/invoices.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,13 @@ html {
</tbody>
</table>
</div>
<% if bank_account %>
<div class="bank-account-details">
<h3>Bank Account Details</h3>
<p>Bank Name: <%= bank_account[:bank_name] %></p>
<p>Account Type: <%= bank_account[:account_type].capitalize %></p>
<p>Routing Number: <%= bank_account[:routing_number] %></p>
<p>Account Number: <%= bank_account[:account_number] %></p>
</div>
<% end %>
</div>
1 change: 1 addition & 0 deletions config/routes/internal_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
get "payments/settings", to: "payment_settings#index"
post "payments/settings/stripe/connect", to: "payment_settings#connect_stripe"
delete "payments/settings/stripe/disconnect", to: "payment_settings#destroy"
put "payments/settings/bank_account", to: "payment_settings#update_bank_account"
get "calendars/redirect", to: "calendars#redirect", as: "redirect"
get "calendars/callback", to: "calendars#callback", as: "callback"
get "calendars/calendars", to: "calendars#calendars", as: "calendars"
Expand Down
14 changes: 14 additions & 0 deletions db/migrate/20240903141939_create_bank_accounts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class CreateBankAccounts < ActiveRecord::Migration[7.1]
def change
create_table :bank_accounts do |t|
t.references :company, null: false, foreign_key: true
t.string :routing_number, null: false
t.string :account_number, null: false
t.string :account_type, null: false
t.string :bank_name, null: false
t.timestamps
end
end
end
14 changes: 13 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

82 changes: 54 additions & 28 deletions spec/policies/payment_settings_policy_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,72 @@

RSpec.describe PaymentSettingsPolicy, type: :policy do
let(:company) { create(:company) }
let(:user) { create(:user, current_workspace_id: company.id) }
let(:admin) { create(:user, current_workspace_id: company.id) }
let(:owner) { create(:user, current_workspace_id: company.id) }
let(:employee) { create(:user, current_workspace_id: company.id) }
let(:book_keeper) { create(:user, current_workspace_id: company.id) }

let(:another_company) { create(:company) }
let(:another_admin) { create(:user, current_workspace_id: another_company.id) }
let(:another_owner) { create(:user, current_workspace_id: another_company.id) }
let(:another_employee) { create(:user, current_workspace_id: another_company.id) }

subject { described_class }

context "when user is an admin" do
before do
create(:employment, company:, user:)
user.add_role :admin, company
end
before do
admin.add_role :admin, company
owner.add_role :owner, company
employee.add_role :employee, company
book_keeper.add_role :book_keeper, company

permissions :index? do
it "is permitted to access index" do
expect(subject).to permit(user, :payment_settings)
end
end
another_admin.add_role :admin, another_company
another_owner.add_role :owner, another_company
another_employee.add_role :employee, another_company
end

context "when user is an employee" do
before do
create(:employment, company:, user:)
user.add_role :employee, company
shared_examples "grants access to admin and owner" do |action|
it "allows admin and owner to perform #{action}" do
expect(subject).to permit(admin, company)
expect(subject).to permit(owner, company)
end
end

permissions :index? do
it "is not permitted to access index" do
expect(subject).not_to permit(user, :payment_settings)
end
shared_examples "denies access to employee and book keeper" do |action|
it "denies #{action} to employee and book keeper" do
expect(subject).not_to permit(employee, company)
expect(subject).not_to permit(book_keeper, company)
end
end

context "when user is a book keeper" do
before do
create(:employment, company:, user:)
user.add_role :book_keeper, company
shared_examples "denies access to another company's users" do |action|
it "denies #{action} to users from another company" do
expect(subject).not_to permit(another_admin, company)
expect(subject).not_to permit(another_owner, company)
expect(subject).not_to permit(another_employee, company)
end
end

permissions :index? do
it "is not permitted to access index" do
expect(subject).not_to permit(user, :payment_settings)
end
end
permissions :index? do
include_examples "grants access to admin and owner", "index"
include_examples "denies access to employee and book keeper", "index"
include_examples "denies access to another company's users", "index"
end

permissions :update_bank_account? do
include_examples "grants access to admin and owner", "update_bank_account"
include_examples "denies access to employee and book keeper", "update_bank_account"
include_examples "denies access to another company's users", "update_bank_account"
end

permissions :destroy? do
include_examples "grants access to admin and owner", "destroy"
include_examples "denies access to employee and book keeper", "destroy"
include_examples "denies access to another company's users", "destroy"
end

permissions :connect_stripe? do
include_examples "grants access to admin and owner", "connect_stripe"
include_examples "denies access to employee and book keeper", "connect_stripe"
include_examples "denies access to another company's users", "connect_stripe"
end
end
Loading