From 21e63135c0acf49e8ca8f2133910294d10db055f Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Sun, 10 Apr 2022 00:50:55 +0530 Subject: [PATCH 1/9] added show action --- .../internal_api/v1/generate_invoice_controller.rb | 11 +++++++++++ app/policies/generate_invoice_policy.rb | 4 ++++ config/routes/internal_api.rb | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index e3883f3e7b..b854dc4c95 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -5,4 +5,15 @@ def index authorize :index, policy_class: GenerateInvoicePolicy render :index, locals: { current_company: }, status: :ok end + + def show + authorize :show, policy_class: GenerateInvoicePolicy + render json: { client: }, status: :ok + end + + private + + def client + @_client ||= Client.find(params[:id]) + end end diff --git a/app/policies/generate_invoice_policy.rb b/app/policies/generate_invoice_policy.rb index 0c013de0c0..3ff5a0fcda 100644 --- a/app/policies/generate_invoice_policy.rb +++ b/app/policies/generate_invoice_policy.rb @@ -4,4 +4,8 @@ class GenerateInvoicePolicy < ApplicationPolicy def index? user_owner_or_admin? end + + def show? + user_owner_or_admin? + end end diff --git a/config/routes/internal_api.rb b/config/routes/internal_api.rb index 500d192eb2..b327bc6582 100644 --- a/config/routes/internal_api.rb +++ b/config/routes/internal_api.rb @@ -14,6 +14,6 @@ resources :reports, only: [:index] resources :workspaces, only: [:update] resources :invoices, only: [:index] - resources :generate_invoice, only: [:index] + resources :generate_invoice, only: [:index, :show] end end From c8266f1c3e82707e57bc353c4a750829c67913f4 Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Tue, 12 Apr 2022 12:56:50 +0530 Subject: [PATCH 2/9] added line_item method for get api --- .../v1/generate_invoice_controller.rb | 3 +- app/models/client.rb | 29 +++++++++++++++++++ db/seeds/08_timesheet_entry.rb | 4 +-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index b854dc4c95..491588de21 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -8,7 +8,8 @@ def index def show authorize :show, policy_class: GenerateInvoicePolicy - render json: { client: }, status: :ok + line_item = client.line_items + render json: { client:, line_item: }, status: :ok end private diff --git a/app/models/client.rb b/app/models/client.rb index e45d61df28..4a96e824a2 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -37,6 +37,35 @@ class Client < ApplicationRecord validates :email, uniqueness: { scope: :company_id }, format: { with: Devise.email_regexp } after_discard :discard_projects + def line_items + line_items = + timesheet_entries.where(bill_status: :unbilled) + .joins( + "INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id + AND timesheet_entries.user_id = project_members.user_id" + ) + .joins("INNER JOIN users ON project_members.user_id = users.id") + .select( + "timesheet_entries.id, + users.first_name as user_first_name, + users.last_name as user_last_name, + timesheet_entries.duration, + timesheet_entries.note, + timesheet_entries.work_date, + project_members.hourly_rate as hourly_rate" + ) + line_items.map do |line_item| + { + id: line_item.id, + name: line_item.user_first_name + " " + line_item.user_last_name, + date: line_item.work_date, + description: line_item.note, + rate: line_item.hourly_rate, + qty: line_item.duration + } + end + end + def total_hours_logged(time_frame = "week") from, to = week_month_year(time_frame) (projects.kept.map { |project| project.timesheet_entries.where(work_date: from..to).sum(:duration) }).sum diff --git a/db/seeds/08_timesheet_entry.rb b/db/seeds/08_timesheet_entry.rb index ac417954f2..2dadee0cf1 100644 --- a/db/seeds/08_timesheet_entry.rb +++ b/db/seeds/08_timesheet_entry.rb @@ -8,7 +8,7 @@ (@timesheet_entry_start_date..@timesheet_entry_end_date).each do |date| TimesheetEntry.create!( user: project_member.user, project: project_member.project, duration: 60, - note: "Worked on #{@project_1_client_1_saeloun_india.name}", work_date: date) + note: "Worked on #{@project_1_client_1_saeloun_india.name}", bill_status: :unbilled, work_date: date) end end @@ -16,7 +16,7 @@ (@timesheet_entry_start_date..@timesheet_entry_end_date).each do |date| TimesheetEntry.create!( user: project_member.user, project: project_member.project, duration: 60, - note: "Worked on #{@project_1_client_1_saeloun_us.name}", work_date: date) + note: "Worked on #{@project_1_client_1_saeloun_us.name}", bill_status: :unbilled, work_date: date) end end # Timesheet Entry End From 401994499f5cc6816b0272162e99e770ab45fb54 Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Tue, 12 Apr 2022 12:58:33 +0530 Subject: [PATCH 3/9] space fix --- app/models/client.rb | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/app/models/client.rb b/app/models/client.rb index 4a96e824a2..45a25e88e4 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -38,22 +38,21 @@ class Client < ApplicationRecord after_discard :discard_projects def line_items - line_items = - timesheet_entries.where(bill_status: :unbilled) - .joins( - "INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id - AND timesheet_entries.user_id = project_members.user_id" - ) - .joins("INNER JOIN users ON project_members.user_id = users.id") - .select( - "timesheet_entries.id, - users.first_name as user_first_name, - users.last_name as user_last_name, - timesheet_entries.duration, - timesheet_entries.note, - timesheet_entries.work_date, - project_members.hourly_rate as hourly_rate" - ) + line_items = timesheet_entries.where(bill_status: :unbilled) + .joins( + "INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id + AND timesheet_entries.user_id = project_members.user_id" + ) + .joins("INNER JOIN users ON project_members.user_id = users.id") + .select( + "timesheet_entries.id, + users.first_name as user_first_name, + users.last_name as user_last_name, + timesheet_entries.duration, + timesheet_entries.note, + timesheet_entries.work_date, + project_members.hourly_rate as hourly_rate" + ) line_items.map do |line_item| { id: line_item.id, From 6d02e28aef43e9ecf1ee089a4df8ff6019aee539 Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Tue, 12 Apr 2022 13:29:36 +0530 Subject: [PATCH 4/9] updated key --- .../internal_api/v1/generate_invoice_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index 491588de21..690d1c4008 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -8,8 +8,8 @@ def index def show authorize :show, policy_class: GenerateInvoicePolicy - line_item = client.line_items - render json: { client:, line_item: }, status: :ok + new_line_item_entries = client.line_items + render json: { client:, new_line_item_entries: }, status: :ok end private From f1ebef9a3cc2ae80215ea973b4ae97b52a3a4956 Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Tue, 12 Apr 2022 19:45:17 +0530 Subject: [PATCH 5/9] added pagy --- .../v1/generate_invoice_controller.rb | 4 +-- app/models/client.rb | 28 ++++++------------- config/initializers/pagy.rb | 2 +- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index 690d1c4008..8f8b28ec95 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -8,8 +8,8 @@ def index def show authorize :show, policy_class: GenerateInvoicePolicy - new_line_item_entries = client.line_items - render json: { client:, new_line_item_entries: }, status: :ok + pagy, new_line_item_entries = pagy(client.new_line_item_entries, items: 50) + render json: { client:, new_line_item_entries:, pagy: pagy_metadata(pagy) }, status: :ok end private diff --git a/app/models/client.rb b/app/models/client.rb index 45a25e88e4..283fd63969 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -37,32 +37,22 @@ class Client < ApplicationRecord validates :email, uniqueness: { scope: :company_id }, format: { with: Devise.email_regexp } after_discard :discard_projects - def line_items - line_items = timesheet_entries.where(bill_status: :unbilled) + def new_line_item_entries + timesheet_entries.where(bill_status: :unbilled) .joins( "INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id AND timesheet_entries.user_id = project_members.user_id" ) .joins("INNER JOIN users ON project_members.user_id = users.id") .select( - "timesheet_entries.id, - users.first_name as user_first_name, - users.last_name as user_last_name, - timesheet_entries.duration, - timesheet_entries.note, - timesheet_entries.work_date, - project_members.hourly_rate as hourly_rate" + "timesheet_entries.id as id, + users.first_name as first_name, + users.last_name as last_name, + timesheet_entries.work_date as date, + timesheet_entries.note as description, + project_members.hourly_rate as rate, + timesheet_entries.duration as qty" ) - line_items.map do |line_item| - { - id: line_item.id, - name: line_item.user_first_name + " " + line_item.user_last_name, - date: line_item.work_date, - description: line_item.note, - rate: line_item.hourly_rate, - qty: line_item.duration - } - end end def total_hours_logged(time_frame = "week") diff --git a/config/initializers/pagy.rb b/config/initializers/pagy.rb index e6dbd26a1a..2161431525 100644 --- a/config/initializers/pagy.rb +++ b/config/initializers/pagy.rb @@ -37,7 +37,7 @@ # Array extra: Paginate arrays efficiently, avoiding expensive array-wrapping and without overriding # See https://ddnexus.github.io/pagy/extras/array -# require 'pagy/extras/array' +require "pagy/extras/array" # Calendar extra: Add pagination filtering by calendar time unit (year, quarter, month, week, day) # See https://ddnexus.github.io/pagy/extras/calendar From 6ac1575fa523a61cb3e0430a2c42f7bedddd9dab Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Tue, 12 Apr 2022 20:02:41 +0530 Subject: [PATCH 6/9] added filter for selected entries --- .../internal_api/v1/generate_invoice_controller.rb | 2 +- app/models/client.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index 8f8b28ec95..96b0d7102c 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -8,7 +8,7 @@ def index def show authorize :show, policy_class: GenerateInvoicePolicy - pagy, new_line_item_entries = pagy(client.new_line_item_entries, items: 50) + pagy, new_line_item_entries = pagy(client.new_line_item_entries(params[:selected_entries]), items: 10) render json: { client:, new_line_item_entries:, pagy: pagy_metadata(pagy) }, status: :ok end diff --git a/app/models/client.rb b/app/models/client.rb index 283fd63969..3aec907458 100644 --- a/app/models/client.rb +++ b/app/models/client.rb @@ -37,7 +37,7 @@ class Client < ApplicationRecord validates :email, uniqueness: { scope: :company_id }, format: { with: Devise.email_regexp } after_discard :discard_projects - def new_line_item_entries + def new_line_item_entries(selected_entries) timesheet_entries.where(bill_status: :unbilled) .joins( "INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id @@ -52,7 +52,7 @@ def new_line_item_entries timesheet_entries.note as description, project_members.hourly_rate as rate, timesheet_entries.duration as qty" - ) + ).where.not(id: selected_entries) end def total_hours_logged(time_frame = "week") From 5cdc866564795e3d3fbe6cb4bf1c5a7c3e57820e Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Tue, 12 Apr 2022 22:50:19 +0530 Subject: [PATCH 7/9] added test for new_line_item_entries method in client model --- spec/models/client_spec.rb | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/spec/models/client_spec.rb b/spec/models/client_spec.rb index d56e16a7ae..7047f1219e 100644 --- a/spec/models/client_spec.rb +++ b/spec/models/client_spec.rb @@ -154,5 +154,61 @@ end end end + + describe "#new_line_item_entries" do + let(:company) { create(:company) } + let(:user) { create(:user) } + let(:client) { create(:client, company:) } + let(:project) { create(:project, client:) } + let(:project_member) { create(:project_member, project:, user:, hourly_rate: 5000) } + + before do + create_list(:timesheet_entry, 5, user:, project:) + end + + context "when no entries are selected" do + let(:selected_entries) { [] } + + it "returns all the line item entries" do + result = + client.timesheet_entries.where(bill_status: :unbilled) + .joins("INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id + AND timesheet_entries.user_id = project_members.user_id") + .joins("INNER JOIN users ON project_members.user_id = users.id") + .select( + "timesheet_entries.id as id, + users.first_name as first_name, + users.last_name as last_name, + timesheet_entries.work_date as date, + timesheet_entries.note as description, + project_members.hourly_rate as rate, + timesheet_entries.duration as qty" + ).where.not(id: selected_entries) + expect(client.new_line_item_entries(selected_entries)).to eq(result) + end + end + + context "when some entries are selected" do + let(:selected_entries) { [ 1, 2 ] } + + it "returns all the line item entries except the entries which are selected" do + result = + client.timesheet_entries.where(bill_status: :unbilled) + .joins("INNER JOIN project_members ON timesheet_entries.project_id = project_members.project_id + AND timesheet_entries.user_id = project_members.user_id") + .joins("INNER JOIN users ON project_members.user_id = users.id") + .select( + "timesheet_entries.id as id, + users.first_name as first_name, + users.last_name as last_name, + timesheet_entries.work_date as date, + timesheet_entries.note as description, + project_members.hourly_rate as rate, + timesheet_entries.duration as qty" + ).where.not(id: selected_entries) + expect(client.new_line_item_entries(selected_entries)).to eq(result) + end + end + end end end From bf5c1a06a7c5c09993dd329dbbf234cc6cae5669 Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Wed, 13 Apr 2022 16:33:45 +0530 Subject: [PATCH 8/9] removed client from the api response --- app/controllers/internal_api/v1/generate_invoice_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/internal_api/v1/generate_invoice_controller.rb b/app/controllers/internal_api/v1/generate_invoice_controller.rb index 96b0d7102c..5422753c4c 100644 --- a/app/controllers/internal_api/v1/generate_invoice_controller.rb +++ b/app/controllers/internal_api/v1/generate_invoice_controller.rb @@ -9,7 +9,7 @@ def index def show authorize :show, policy_class: GenerateInvoicePolicy pagy, new_line_item_entries = pagy(client.new_line_item_entries(params[:selected_entries]), items: 10) - render json: { client:, new_line_item_entries:, pagy: pagy_metadata(pagy) }, status: :ok + render json: { new_line_item_entries:, pagy: pagy_metadata(pagy) }, status: :ok end private From 62b7d9ee0437cacb5a431bbb584af0fefbe9d396 Mon Sep 17 00:00:00 2001 From: Aniket Kaushik Date: Wed, 13 Apr 2022 16:55:16 +0530 Subject: [PATCH 9/9] added test for generate invoice controller show action --- .../v1/generate_invoice/show_spec.rb | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 spec/requests/internal_api/v1/generate_invoice/show_spec.rb diff --git a/spec/requests/internal_api/v1/generate_invoice/show_spec.rb b/spec/requests/internal_api/v1/generate_invoice/show_spec.rb new file mode 100644 index 0000000000..31220adc89 --- /dev/null +++ b/spec/requests/internal_api/v1/generate_invoice/show_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "InternalApi::V1::GeneratInvoice#show", type: :request do + let(:company) { create(:company) } + let(:user) { create(:user, current_workspace_id: company.id) } + let(:client) { create(:client, company:) } + let(:project) { create(:project, client:) } + let(:project_member) { create(:project_member, user:, project:, hourly_rate: 5000) } + + context "when user is admin" do + before do + create(:company_user, company:, user:) + user.add_role :admin, company + sign_in user + create_list(:timesheet_entry, 5, user:, project:) + send_request :get, internal_api_v1_generate_invoice_path(client) + end + + context "when no entries are selected" do + let(:selected_entries) { [] } + + it "returns the all new_line_item_entries" do + new_line_item_entries = client.new_line_item_entries(selected_entries) + expect(response).to have_http_status(:ok) + expect(json_response["new_line_item_entries"]).to eq(JSON.parse(new_line_item_entries.to_json)) + end + end + + context "when some entries are selected" do + let(:selected_entries) { [1, 2] } + + it "returns the all new_line_item_entries except the one whcih are selected" do + new_line_item_entries = client.new_line_item_entries(selected_entries) + expect(response).to have_http_status(:ok) + expect(json_response["new_line_item_entries"]).to eq(JSON.parse(new_line_item_entries.to_json)) + 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 :get, internal_api_v1_generate_invoice_index_path + end + + it "is not permitted to view time entry report" do + expect(response).to have_http_status(:forbidden) + end + end + + context "when unauthenticated" do + it "is not permitted to view time entry report" do + send_request :get, internal_api_v1_reports_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