Skip to content

Commit

Permalink
[BE]APIs to save invoices (#256)
Browse files Browse the repository at this point in the history
* API for invoice creation

* API to update an invoice

* Remove unnecessary if clause

* use partials to dry up

* abstract client.find and fix specs
  • Loading branch information
Apoorv Mishra authored Apr 14, 2022
1 parent c7ea5ff commit 6b8638a
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 1 deletion.
33 changes: 33 additions & 0 deletions app/controllers/internal_api/v1/invoices_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,37 @@ def index
}
}
end

def create
authorize Invoice
render :create, locals: {
invoice: Invoice.create!(invoice_params),
client: load_client(invoice_params[:client_id])
}
end

def update
authorize invoice
invoice.update!(invoice_params)
render :update, locals: {
invoice:,
client: load_client(invoice[:client_id])
}
end

private

def load_client(client_id)
Client.find(client_id)
end

def invoice
@_invoice ||= Invoice.find(params[:id])
end

def invoice_params
params.require(:invoice).permit(
policy(Invoice).permitted_attributes
)
end
end
17 changes: 17 additions & 0 deletions app/policies/invoice_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,24 @@ def index?
user_owner_or_admin?
end

def create?
user_owner_or_admin?
end

def update?
user_owner_or_admin?
end

def show?
user_owner_or_admin?
end

def permitted_attributes
[
:issue_date, :due_date,
:invoice_number, :reference, :amount,
:outstanding_amount, :tax, :amount_paid,
:amount_due, :discount, :client_id
]
end
end
20 changes: 20 additions & 0 deletions app/views/internal_api/v1/invoices/_invoice.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

json.key_format! camelize: :lower
json.deep_format_keys!

json.id invoice.id
json.invoice_number invoice.invoice_number
json.issue_date invoice.issue_date
json.due_date invoice.due_date
json.reference invoice.reference
json.amount invoice.amount
json.outstanding_amount invoice.outstanding_amount
json.amount_paid invoice.amount_paid
json.amount_due invoice.amount_due
json.discount invoice.discount
json.tax invoice.tax
json.status invoice.status
json.client do
json.name client.name
end
3 changes: 3 additions & 0 deletions app/views/internal_api/v1/invoices/create.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

json.partial! "invoice", locals: { invoice:, client: }
3 changes: 3 additions & 0 deletions app/views/internal_api/v1/invoices/update.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# frozen_string_literal: true

json.partial! "invoice", locals: { invoice:, client: }
2 changes: 1 addition & 1 deletion config/routes/internal_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
resources :timesheet_entry, only: [:index, :create, :update, :destroy]
resources :reports, only: [:index]
resources :workspaces, only: [:update]
resources :invoices, only: [:index]
resources :invoices, only: [:index, :create, :update]
resources :generate_invoice, only: [:index, :show]
end
end
70 changes: 70 additions & 0 deletions spec/requests/internal_api/v1/invoices/create_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "InternalApi::V1::Invoices#create", type: :request do
let(:company) do
create(:company, clients: create_list(:client_with_invoices, 5))
end

let(:user) { create(:user, current_workspace_id: company.id) }

context "when user is admin" do
before do
create(:company_user, company:, user:)
user.add_role :admin, company
sign_in user
end

describe "invoice creation" do
it "creates invoice successfully" do
invoice = attributes_for(
:invoice,
client: company.clients.first,
client_id: company.clients.first.id,
status: :draft)
send_request :post, internal_api_v1_invoices_path(invoice:)
expect(response).to have_http_status(:ok)
expected_attrs = ["amount", "amountDue", "amountPaid",
"client", "discount", "dueDate", "id",
"invoiceNumber", "issueDate", "outstandingAmount",
"reference", "status", "tax"]
expect(json_response.keys.sort).to match(expected_attrs)
end

it "throws 422 if client doesn't exist" do
send_request :post, internal_api_v1_invoices_path(
invoice: {
client_id: 100000,
invoice_number: "INV0001",
reference: "bar",
issue_date: "2022-01-01",
due_date: "2022-01-31"
})
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response["errors"]["client"].first).to eq("must exist")
end
end
end

context "when user is employee" do
before do
create(:company_user, company:, user:)
user.add_role :employee, company
sign_in user
send_request :post, internal_api_v1_invoices_path
end

it "is not be permitted to generate an invoice" do
expect(response).to have_http_status(:forbidden)
end
end

context "when unauthenticated" do
it "is not be permitted to generate an invoice" do
send_request :post, internal_api_v1_invoices_path
expect(response).to have_http_status(:unauthorized)
expect(json_response["error"]).to eq("You need to sign in or sign up before continuing.")
end
end
end
65 changes: 65 additions & 0 deletions spec/requests/internal_api/v1/invoices/update_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "InternalApi::V1::Invoices#update", type: :request do
let(:company) do
create(:company, clients: create_list(:client_with_invoices, 5))
end

let(:user) { create(:user, current_workspace_id: company.id) }

context "when user is admin" do
before do
create(:company_user, company:, user:)
user.add_role :admin, company
sign_in user
end

describe "invoice updation" do
it "updates invoice successfully" do
send_request :patch, internal_api_v1_invoice_path(
id: company.clients.first.invoices.first.id, params: {
invoice: {
reference: "foo"
}
})
expect(response).to have_http_status(:ok)
expect(json_response["reference"]).to eq("foo")
end

it "throws 422 if client doesn't exist" do
send_request :patch, internal_api_v1_invoice_path(
id: company.clients.first.invoices.first.id, params: {
invoice: {
client_id: 100000,
reference: "foo"
}
})
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response["errors"]["client"].first).to eq("must exist")
end
end
end

context "when user is employee" do
before do
create(:company_user, company:, user:)
user.add_role :employee, company
sign_in user
send_request :patch, internal_api_v1_invoice_path(id: company.clients.first.invoices.first.id)
end

it "is not be permitted to update an invoice" do
expect(response).to have_http_status(:forbidden)
end
end

context "when unauthenticated" do
it "is not be permitted to update an invoice" do
send_request :patch, internal_api_v1_invoice_path(id: 1)
expect(response).to have_http_status(:unauthorized)
expect(json_response["error"]).to eq("You need to sign in or sign up before continuing.")
end
end
end

0 comments on commit 6b8638a

Please sign in to comment.