diff --git a/test/fixtures/api_actions.yml b/test/fixtures/api_actions.yml index 18a51c5d8..2d22b77bc 100644 --- a/test/fixtures/api_actions.yml +++ b/test/fixtures/api_actions.yml @@ -124,3 +124,89 @@ create_api_action_plugin_bishop_monitoring_email: include_api_resource_data: true email: test@restarone.com api_namespace: monitoring_target_incident + +custom_api_action_on_timesheet_request: + type: CreateApiAction + action_type: custom_action + include_api_resource_data: true + payload_mapping: + api_namespace: timesheet_request + method_definition: | + # get time tracker namespace + time_tracker = ApiNamespace.find_by(name: 'time_tracker') + + format = "%Y-%m-%d" + if api_resource.properties['current_month'] == "true" + api_resource.update(properties: api_resource.properties.merge({ start_date: Date.today.beginning_of_month.strftime(format), end_date: Date.today.end_of_month.strftime(format) })) + end + + # get time tracker entries for the current user within the specified time + start_date = Date.parse(api_resource.properties['start_date']) + end_date = Date.parse(api_resource.properties['end_date']) + email = current_user.email + + current_user_time_tracker_entries = ApiResource.where([ + "api_namespace_id = :api_namespace_id + and created_at::date >= :start_date + and created_at::date <= :end_date + and properties->>'email_address' = :email", + { + api_namespace_id: time_tracker.id, + start_date: start_date, + end_date: end_date, + email: email + } + ]) + + # add email of user requesting timesheet + api_resource.update(properties: api_resource.properties.merge({ requested_by: email })) + + # get the consultant rate and check if they are active + consultant = ApiNamespace.includes(:api_resources).find_by(name: 'consultant').api_resources.find { |consultant| consultant.properties['email'] == email } + + send_email = ->(email, subject, content, attachment) { + email_thread = MessageThread.create(recipients: [email], subject: subject) + email_message = email_thread.messages.create(content: content, from: "noreply@#{ENV["APP_HOST"]}") + attachments = !attachment ? [] : [attachment] + + EMailer.with(message: email_message, message_thread: email_thread, attachments: attachments).ship.deliver_later + } + + if consultant.nil? + send_email.(email, "Unable to generate timesheet", "Please register as a consultant and add your work hours to request timesheet.", nil) + return + end + + if consultant.properties['active'].to_s.downcase == "false" + send_email.(email, "Unable to generate timesheet", "Inactive consultants cannot request timesheet. Please contact administrator.", nil) + return + end + + rate = Float(consultant.properties['rate'].to_s, exception: false) + if rate == nil || rate <= 0 + send_email.(email, "Unable to generate timesheet", "Invalid hourly rate for consultant.", nil) + return + end + + total_hours = current_user_time_tracker_entries.sum { |entry| entry.properties['how_much_time_in_hours_spent'].to_d } + + # construct the csv file + csv_string = CSV.generate do |csv| + csv << ['', 'Rate', rate] + csv << ['', 'Total Hours', total_hours] + csv << ['', 'Grand Total', total_hours * rate] + csv << ['', '', ''] + csv << ['Date', 'Hours', 'User', 'Client', 'Task', 'Notes'] + current_user_time_tracker_entries.map { |entry| csv << [entry.created_at.to_s, entry.properties['how_much_time_in_hours_spent'], entry.properties['email_address'], entry.properties['for_what_client'], entry.properties['what_task_did_you_work_on'], entry.properties['notes']] } + end + + # email the created csv file as an attachment + email_content = csv_string.html_safe + subject = "Timesheet for #{email} from #{api_resource.properties['start_date']} to #{api_resource.properties['end_date']}" + attachment = { filename: "#{subject}.csv", mime_type: "text/csv", content: email_content } + content = "Please find your timesheet attached to this email " + + blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(attachment[:content]), filename: attachment[:filename], content_type: attachment[:mime_type], metadata: nil) + content += ActionText::Content.new("").to_s + + send_email.(email, subject, content, attachment) \ No newline at end of file diff --git a/test/fixtures/api_namespaces.yml b/test/fixtures/api_namespaces.yml index 2ff093895..732223e01 100755 --- a/test/fixtures/api_namespaces.yml +++ b/test/fixtures/api_namespaces.yml @@ -226,3 +226,28 @@ namespace_with_transcript: "transcript": "", "transcript_parsed": false } + +time_tracker: + name: time_tracker + slug: time-tracker + version: 1 + properties: + { + "notes": "", + "for_what_client": "", + "what_task_did_you_work_on": "", + "how_much_time_in_hours_spent": 0, + "i_certify_information_accurate": false, + } + +consultant: + name: consultant + slug: consultant + version: 1 + properties: { "active": false, "name": "", "rate": 0, "email": "" } + +timesheet_request: + name: consultant/timesheet_request + slug: consultant-timesheet_request + version: 1 + properties: { "start_date": "", "end_date": "", "current_month": true } diff --git a/test/fixtures/api_resources.yml b/test/fixtures/api_resources.yml index b03d7202a..000ac4966 100755 --- a/test/fixtures/api_resources.yml +++ b/test/fixtures/api_resources.yml @@ -95,3 +95,63 @@ transcript: "transcript": "", "transcript_parsed": false } + +consultant_one: + api_namespace: consultant + properties: + { + "active": true, + "name": "Test User", + "rate": 20.0, + "email": "test@restarone.solutions", + } + +tracker_entry_one: + api_namespace: time_tracker + properties: + { + "notes": "http://link-to-backlog-task-1", + "for_what_client": "Retarone Solutions", + "what_task_did_you_work_on": "Setup 2FA", + "how_much_time_in_hours_spent": 7.3, + "i_certify_information_accurate": true, + "email_address": "test@restarone.solutions", + } + +previous_month_tracker_entry_two: + api_namespace: time_tracker + created_at: <%= 1.month.ago %> + properties: + { + "notes": "http://link-to-backlog-task-2", + "for_what_client": "Retarone Solutions", + "what_task_did_you_work_on": "Add proper form validations in user login", + "how_much_time_in_hours_spent": 4.5, + "i_certify_information_accurate": true, + "email_address": "test@restarone.solutions", + } + +tracker_entry_three: + api_namespace: time_tracker + properties: + { + "notes": "http://link-to-backlog-task-3", + "for_what_client": "Retarone Solutions", + "what_task_did_you_work_on": "Setup CI/CD with GitLab", + "how_much_time_in_hours_spent": 3.5, + "i_certify_information_accurate": true, + "email_address": "test@restarone.solutions", + } + +timesheet_request_date_range_previous_month: + api_namespace: timesheet_request + properties: + { + "start_date": <%= 1.month.ago.beginning_of_month %>, + "end_date": <%= 1.month.ago.end_of_month %>, + "current_month": "false", + } + +timesheet_request_current_month: + api_namespace: timesheet_request + properties: { "start_date": "", "end_date": "", "current_month": "true" } diff --git a/test/plugins/timesheet_request_plugin_test.rb b/test/plugins/timesheet_request_plugin_test.rb new file mode 100644 index 000000000..7264a56d6 --- /dev/null +++ b/test/plugins/timesheet_request_plugin_test.rb @@ -0,0 +1,152 @@ +require "test_helper" + +class TimesheetRequestPluginTest < ActiveSupport::TestCase + setup do + @consultant = api_resources(:consultant_one) + @timesheet_request = api_namespaces(:timesheet_request) + @api_action = api_actions(:custom_api_action_on_timesheet_request) + + @api_resource_date_range = api_resources(:timesheet_request_date_range_previous_month) + @api_resource_current_month = api_resources(:timesheet_request_current_month) + + # Setting current_user for custom_api_action + Current.user = users(:one) # test@restarone.solutions + + Sidekiq::Testing::fake! + end + + test "Should send email on requesting timesheet" do + @api_action.update({ + api_resource_id: @api_resource_current_month.id + }) + + perform_enqueued_jobs do + @api_action.execute_action + end + + sent_email = ActionMailer::Base.deliveries.last + + assert_equal 1, sent_email.attachments.length + end + + test "Should get all the hours for current month" do + @api_action.update({ + api_resource_id: @api_resource_current_month.id + }) + + perform_enqueued_jobs do + @api_action.execute_action + end + + sent_email = ActionMailer::Base.deliveries.last + csv_file = sent_email.attachments[0] + + parsed_csv = CSV.parse(csv_file.body.raw_source.gsub(/\r\n/, "$"), col_sep: ",", row_sep: "$") + + rate = @consultant.properties["rate"] + assert_equal rate, parsed_csv[0][2].to_d + + entries = [ + api_resources(:tracker_entry_one), + api_resources(:tracker_entry_three) + ] + + csv_tasks = [] + entries.each { | entry | csv_tasks.push([ + entry.created_at.to_s, + entry.properties["how_much_time_in_hours_spent"].to_s, + entry.properties["email_address"], + entry.properties["for_what_client"], + entry.properties["what_task_did_you_work_on"], + entry.properties["notes"] + ]) } + + csv_tasks.each { | task | assert parsed_csv.include?(task) } + + grand_total = 0 + entries.each { | entry | grand_total += entry.properties["how_much_time_in_hours_spent"]} + grand_total *= rate + assert_equal grand_total, parsed_csv[2][2].to_d + end + + test "Should get all the hours for specified time period" do + @api_action.update!({ + api_resource_id: @api_resource_date_range.id, + }) + + perform_enqueued_jobs do + @api_action.execute_action + end + + sent_email = ActionMailer::Base.deliveries.last + csv_file = sent_email.attachments[0] + + parsed_csv = CSV.parse(csv_file.body.raw_source.gsub(/\r\n/, "$"), col_sep: ",", row_sep: "$") + rate = @consultant.properties["rate"] + assert_equal rate, parsed_csv[0][2].to_d + + entry_one = api_resources(:previous_month_tracker_entry_two) + assert_equal entry_one.properties["how_much_time_in_hours_spent"], parsed_csv[1][2].to_d + assert_equal entry_one.properties["for_what_client"], parsed_csv[5][3] + assert_equal entry_one.properties["notes"], parsed_csv[5][5] + + grand_total = entry_one.properties["how_much_time_in_hours_spent"] * rate + assert_equal grand_total, parsed_csv[2][2].to_d + end + + test "Should send an email without timesheet for inactive consultants" do + @consultant.update({ + properties: @consultant.properties.merge({ active: false }) + }) + + @api_action.update!({ + api_resource_id: @api_resource_date_range.id, + }) + + perform_enqueued_jobs do + @api_action.execute_action + end + + sent_email = ActionMailer::Base.deliveries.last + assert_equal 0, sent_email.attachments.length + assert_equal "Unable to generate timesheet", sent_email.subject + assert sent_email.body.include?("Inactive consultants cannot request timesheet") + end + + test "Should send an email without timesheet if consultant's rate is invalid" do + @consultant.update({ + properties: @consultant.properties.merge({ rate: "20.0$" }) + }) + + @api_action.update!({ + api_resource_id: @api_resource_date_range.id, + }) + + perform_enqueued_jobs do + @api_action.execute_action + end + + sent_email = ActionMailer::Base.deliveries.last + assert_equal 0, sent_email.attachments.length + assert_equal "Unable to generate timesheet", sent_email.subject + assert sent_email.body.include?("Invalid hourly rate for consultant") + end + + + test "Should send an email without timesheet for non existing consultants" do + @consultant.delete + + @api_action.update!({ + api_resource_id: @api_resource_date_range.id, + }) + + perform_enqueued_jobs do + @api_action.execute_action + end + + sent_email = ActionMailer::Base.deliveries.last + assert_equal 0, sent_email.attachments.length + assert_equal "Unable to generate timesheet", sent_email.subject + assert sent_email.body.include?("Please register as a consultant") + end +end