Compare commits
10 Commits
a697bbf7be
...
213cc31275
Author | SHA1 | Date |
---|---|---|
Trevor Vallender | 213cc31275 | |
Trevor Vallender | a74d1ad2ea | |
Trevor Vallender | bcb669745e | |
Trevor Vallender | a71ffbcacc | |
Trevor Vallender | 40eb43e0c5 | |
Trevor Vallender | d81982cd20 | |
Trevor Vallender | 543dc1572a | |
Trevor Vallender | 14c8f6633d | |
Trevor Vallender | 244c8f1a07 | |
Trevor Vallender | 3e04193d16 |
|
@ -26,10 +26,12 @@ class JobsController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
if @job = Job.create(job_params)
|
||||
@job = Job.new(remove_empty_time_budgets(job_params))
|
||||
if @job.save
|
||||
redirect_to project_job_path(@job.project, @job)
|
||||
else
|
||||
render :edit
|
||||
@project = @job.project
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,9 @@ module JobsHelper
|
|||
end
|
||||
|
||||
def progress_bar_for(budget)
|
||||
l_hours_short(budget.hours)
|
||||
progress_bar(budget.done_ratio,
|
||||
legend: "#{budget.done_ratio}%
|
||||
(#{l_hours_short(budget.total_time_logged)}/#{l_hours_short(budget.hours)})",
|
||||
class: "progress")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,12 +35,6 @@ class Job < ActiveRecord::Base
|
|||
time_budgets.sum(&:hours)
|
||||
end
|
||||
|
||||
def time_budget_for(category)
|
||||
return 0 if category.nil? || time_budgets.find_by(category_id: category.id).nil?
|
||||
|
||||
time_budgets.find_by(category_id: category.id).hours
|
||||
end
|
||||
|
||||
def total_time_logged
|
||||
TimeEntry.where(job_id: id)
|
||||
.sum(:hours)
|
||||
|
@ -56,4 +50,27 @@ class Job < ActiveRecord::Base
|
|||
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
|
||||
ActionController::Base.helpers.link_to name, ActionController::Base.helpers.project_job_path(project, self)
|
||||
end
|
||||
|
||||
def self.fields_for_order_statement
|
||||
"jobs.name"
|
||||
end
|
||||
|
||||
def self.default_for(time_entry)
|
||||
projects = [time_entry.project, time_entry.project.parent]
|
||||
jobs = Job.where(project: projects).active
|
||||
support = jobs.where(category: JobCategory.support).first
|
||||
retainer = jobs.where(category: JobCategory.retainer).first
|
||||
sprints = jobs.where(category: JobCategory.sprints).first
|
||||
priority_list = [sprints, retainer, support].compact
|
||||
|
||||
return jobs.first if priority_list.empty?
|
||||
|
||||
return support if time_entry.activity.name == "Support"
|
||||
|
||||
return priority_list.first if time_entry.issue.blank?
|
||||
|
||||
return support if time_entry.issue.tracker.name == "Support"
|
||||
|
||||
priority_list.first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,6 +5,10 @@ class JobCategory < Enumeration
|
|||
|
||||
OptionName = :enumeration_job_category
|
||||
|
||||
scope :support, -> { where(name: 'Support').first }
|
||||
scope :retainer, -> { where(name: 'Retainer').first }
|
||||
scope :sprints, -> { where(name: 'Sprints').first }
|
||||
|
||||
def option_name
|
||||
OptionName
|
||||
end
|
||||
|
|
|
@ -10,4 +10,18 @@ class TimeBudget < ActiveRecord::Base
|
|||
|
||||
belongs_to :job
|
||||
belongs_to :category, class_name: "TimeBudgetCategory"
|
||||
|
||||
def done_ratio
|
||||
return 0 if hours.zero?
|
||||
|
||||
(total_time_logged / hours * 100).to_i
|
||||
end
|
||||
|
||||
def total_time_logged
|
||||
TimeEntry.joins(:user)
|
||||
.where(
|
||||
job_id: job_id,
|
||||
users: { time_budget_category_id: category_id }
|
||||
).sum(:hours)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<%= f.hidden_field :project_id, value: @job.project.id %>
|
||||
|
||||
<fieldset>
|
||||
<legend>Budget</legend>
|
||||
<legend>Budget (hours)</legend>
|
||||
<%= f.fields_for :time_budgets do |ff| %>
|
||||
<p>
|
||||
<%= ff.label :hours, ff.object.category&.name || "Unassigned" %>
|
||||
|
|
|
@ -3,10 +3,6 @@
|
|||
<td><%= job.category&.name || "Unassigned" %></td>
|
||||
<td><%= format_date(job.starts_on) %></td>
|
||||
<td><%= format_date(job.ends_on) %></td>
|
||||
<td><%= job.project_id %></td>
|
||||
<td>
|
||||
<%= l_hours_short(job.total_time_budget) %>
|
||||
</td>
|
||||
<td>
|
||||
<%= total_progress_bar(job) %>
|
||||
</td>
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
<th>Category</th>
|
||||
<th>Starts on</th>
|
||||
<th>Ends on</th>
|
||||
<th>Project</th>
|
||||
<th>External project</th>
|
||||
<th>Progress</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<% content_for :header_tags do %>
|
||||
<%= stylesheet_link_tag "jobs", plugin: "jobs" %>
|
||||
<% end %>
|
||||
|
||||
<% html_title @job.name %>
|
||||
<div class="contextual">
|
||||
<%= link_to 'Edit', edit_project_job_path(@project, @job), class: "icon icon-edit edit-job" %>
|
||||
|
@ -7,7 +11,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
<h2>Job #<%= @job.id %></h2>
|
||||
<div class="issue">
|
||||
<div class="job">
|
||||
<div class="subject"><h3><%= @job.name %></h3></div>
|
||||
<p><%= @job.description %></p>
|
||||
<div class="attributes">
|
||||
|
@ -19,7 +23,7 @@
|
|||
<div class="label">Starts on:</div>
|
||||
<div class="value"><%= format_date(@job.starts_on) %></div>
|
||||
<div class="label">Ends on:</div>
|
||||
<div class="value"><%= format_date(@job.starts_on) %></div>
|
||||
<div class="value"><%= format_date(@job.ends_on) %></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="splitcontentright">
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<p>
|
||||
<%= form.label :job_id %>
|
||||
<%= form.collection_select :job_id, Job.project_or_parent(@project), :id, :name %>
|
||||
<%= form.collection_select :job_id, Job.active.project_or_parent(@project), :id, :name, include_blank: true %>
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/* These are a copy of the default CSS for issues */
|
||||
div.job {background:#ffffdd; padding:6px; margin-bottom:6px; border: 1px solid #d7d7d7; border-radius:3px;}
|
||||
div.job div.subject div div { padding-left: 16px; word-break: break-word; }
|
||||
div.job div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
|
||||
div.job div.subject>div>p { margin-top: 0.5em; }
|
||||
div.job div.subject h3 {margin: 0; margin-bottom: 0.1em;}
|
||||
div.job p.author {margin-top:0.5em;}
|
||||
div.job span.private, div.journal span.private {font-size: 60%;}
|
||||
div.job .next-prev-links {color:#999;}
|
||||
div.job .attributes {margin-top: 2em;}
|
||||
div.job .attributes .attribute {padding-left:180px; clear:left; min-height: 1.8em;}
|
||||
div.job .attributes .attribute .label {width: 170px; margin-left:-180px; font-weight:bold; float:left; overflow: clip visible; text-overflow: ellipsis;}
|
||||
div.job .attribute .value {overflow:auto; text-overflow: ellipsis;}
|
||||
div.job .attribute.string_cf .value .wiki p {margin-top: 0; margin-bottom: 0;}
|
||||
div.job .attribute.text_cf .value .wiki p:first-of-type {margin-top: 0;}
|
||||
div.job.overdue .due-date .value { color: #c22; }
|
|
@ -0,0 +1,5 @@
|
|||
class RemoveUniqueNameConstraintFromJobs < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
remove_index :jobs, :name
|
||||
end
|
||||
end
|
5
init.rb
5
init.rb
|
@ -6,7 +6,6 @@ Redmine::Plugin.register :jobs do
|
|||
url 'http://tsvallender.co.uk'
|
||||
author_url 'http://tsvallender.co.uk'
|
||||
|
||||
permission :jobs, { jobs: [:index, :show, :new, :create, :edit, :update, :destroy] }, public: false
|
||||
menu :project_menu, :jobs, { controller: 'jobs', action: 'index' }, caption: 'Jobs', after: :issues, param: :project_id
|
||||
|
||||
TimeEntry.safe_attributes 'job_id'
|
||||
|
@ -18,4 +17,8 @@ Redmine::Plugin.register :jobs do
|
|||
TimeEntry.send(:include, TimeEntryPatch)
|
||||
Project.send(:include, ProjectPatch)
|
||||
end
|
||||
|
||||
project_module :jobs do
|
||||
permission :jobs, { jobs: [:index, :show, :new, :create, :edit, :update, :destroy] }, public: false
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,5 +7,13 @@ module TimeEntryPatch
|
|||
|
||||
included do
|
||||
belongs_to :job
|
||||
|
||||
before_save :set_job, unless: :job
|
||||
|
||||
def set_job
|
||||
return if job.present?
|
||||
|
||||
self.job = Job.default_for(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,9 @@ module TimeEntryQueryPatch
|
|||
|
||||
alias_method :available_columns_without_jobs, :available_columns
|
||||
alias_method :available_columns, :available_columns_with_jobs
|
||||
|
||||
alias_method :joins_for_order_statement_without_jobs, :joins_for_order_statement
|
||||
alias_method :joins_for_order_statement, :joins_for_order_statement_with_jobs
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
@ -25,7 +28,7 @@ module TimeEntryQueryPatch
|
|||
def available_columns_with_jobs
|
||||
if @available_columns.nil?
|
||||
@available_columns = available_columns_without_jobs
|
||||
@available_columns << QueryColumn.new(:job)
|
||||
@available_columns << QueryColumn.new(:job, groupable: true, sortable: -> { Job.fields_for_order_statement })
|
||||
else
|
||||
available_columns_without_jobs
|
||||
end
|
||||
|
@ -48,5 +51,16 @@ module TimeEntryQueryPatch
|
|||
[job.name, job.id.to_s]
|
||||
end
|
||||
end
|
||||
|
||||
def joins_for_order_statement_with_jobs(order_options)
|
||||
joins = joins_for_order_statement_without_jobs(order_options) || ""
|
||||
|
||||
if order_options
|
||||
if order_options.include?('jobs')
|
||||
joins += " LEFT OUTER JOIN #{Job.table_name} ON #{Job.table_name}.id = #{TimeEntry.table_name}.job_id"
|
||||
end
|
||||
end
|
||||
joins
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue