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
|
end
|
||||||
|
|
||||||
def create
|
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)
|
redirect_to project_job_path(@job.project, @job)
|
||||||
else
|
else
|
||||||
render :edit
|
@project = @job.project
|
||||||
|
render :new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,13 @@ module JobsHelper
|
||||||
progress_bar(job.done_ratio,
|
progress_bar(job.done_ratio,
|
||||||
legend: "#{job.done_ratio}%
|
legend: "#{job.done_ratio}%
|
||||||
(#{l_hours_short(job.total_time_logged)}/#{l_hours_short(job.total_time_budget)})",
|
(#{l_hours_short(job.total_time_logged)}/#{l_hours_short(job.total_time_budget)})",
|
||||||
class: "progress")
|
class: "progress")
|
||||||
end
|
end
|
||||||
|
|
||||||
def progress_bar_for(budget)
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,12 +35,6 @@ class Job < ActiveRecord::Base
|
||||||
time_budgets.sum(&:hours)
|
time_budgets.sum(&:hours)
|
||||||
end
|
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
|
def total_time_logged
|
||||||
TimeEntry.where(job_id: id)
|
TimeEntry.where(job_id: id)
|
||||||
.sum(:hours)
|
.sum(:hours)
|
||||||
|
@ -56,4 +50,27 @@ class Job < ActiveRecord::Base
|
||||||
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
|
ActionView::Base.send(:include, Rails.application.routes.url_helpers)
|
||||||
ActionController::Base.helpers.link_to name, ActionController::Base.helpers.project_job_path(project, self)
|
ActionController::Base.helpers.link_to name, ActionController::Base.helpers.project_job_path(project, self)
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -5,6 +5,10 @@ class JobCategory < Enumeration
|
||||||
|
|
||||||
OptionName = :enumeration_job_category
|
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
|
def option_name
|
||||||
OptionName
|
OptionName
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,4 +10,18 @@ class TimeBudget < ActiveRecord::Base
|
||||||
|
|
||||||
belongs_to :job
|
belongs_to :job
|
||||||
belongs_to :category, class_name: "TimeBudgetCategory"
|
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
|
end
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<%= f.hidden_field :project_id, value: @job.project.id %>
|
<%= f.hidden_field :project_id, value: @job.project.id %>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Budget</legend>
|
<legend>Budget (hours)</legend>
|
||||||
<%= f.fields_for :time_budgets do |ff| %>
|
<%= f.fields_for :time_budgets do |ff| %>
|
||||||
<p>
|
<p>
|
||||||
<%= ff.label :hours, ff.object.category&.name || "Unassigned" %>
|
<%= ff.label :hours, ff.object.category&.name || "Unassigned" %>
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
<td><%= job.category&.name || "Unassigned" %></td>
|
<td><%= job.category&.name || "Unassigned" %></td>
|
||||||
<td><%= format_date(job.starts_on) %></td>
|
<td><%= format_date(job.starts_on) %></td>
|
||||||
<td><%= format_date(job.ends_on) %></td>
|
<td><%= format_date(job.ends_on) %></td>
|
||||||
<td><%= job.project_id %></td>
|
|
||||||
<td>
|
|
||||||
<%= l_hours_short(job.total_time_budget) %>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<%= total_progress_bar(job) %>
|
<%= total_progress_bar(job) %>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th>Starts on</th>
|
<th>Starts on</th>
|
||||||
<th>Ends on</th>
|
<th>Ends on</th>
|
||||||
<th>Project</th>
|
|
||||||
<th>External project</th>
|
|
||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
<% content_for :header_tags do %>
|
||||||
|
<%= stylesheet_link_tag "jobs", plugin: "jobs" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<% html_title @job.name %>
|
<% html_title @job.name %>
|
||||||
<div class="contextual">
|
<div class="contextual">
|
||||||
<%= link_to 'Edit', edit_project_job_path(@project, @job), class: "icon icon-edit edit-job" %>
|
<%= link_to 'Edit', edit_project_job_path(@project, @job), class: "icon icon-edit edit-job" %>
|
||||||
|
@ -7,7 +11,7 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<h2>Job #<%= @job.id %></h2>
|
<h2>Job #<%= @job.id %></h2>
|
||||||
<div class="issue">
|
<div class="job">
|
||||||
<div class="subject"><h3><%= @job.name %></h3></div>
|
<div class="subject"><h3><%= @job.name %></h3></div>
|
||||||
<p><%= @job.description %></p>
|
<p><%= @job.description %></p>
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
|
@ -19,7 +23,7 @@
|
||||||
<div class="label">Starts on:</div>
|
<div class="label">Starts on:</div>
|
||||||
<div class="value"><%= format_date(@job.starts_on) %></div>
|
<div class="value"><%= format_date(@job.starts_on) %></div>
|
||||||
<div class="label">Ends 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>
|
</div>
|
||||||
<div class="splitcontentright">
|
<div class="splitcontentright">
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<p>
|
<p>
|
||||||
<%= form.label :job_id %>
|
<%= 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'
|
url 'http://tsvallender.co.uk'
|
||||||
author_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
|
menu :project_menu, :jobs, { controller: 'jobs', action: 'index' }, caption: 'Jobs', after: :issues, param: :project_id
|
||||||
|
|
||||||
TimeEntry.safe_attributes 'job_id'
|
TimeEntry.safe_attributes 'job_id'
|
||||||
|
@ -18,4 +17,8 @@ Redmine::Plugin.register :jobs do
|
||||||
TimeEntry.send(:include, TimeEntryPatch)
|
TimeEntry.send(:include, TimeEntryPatch)
|
||||||
Project.send(:include, ProjectPatch)
|
Project.send(:include, ProjectPatch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
project_module :jobs do
|
||||||
|
permission :jobs, { jobs: [:index, :show, :new, :create, :edit, :update, :destroy] }, public: false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,5 +7,13 @@ module TimeEntryPatch
|
||||||
|
|
||||||
included do
|
included do
|
||||||
belongs_to :job
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,9 @@ module TimeEntryQueryPatch
|
||||||
|
|
||||||
alias_method :available_columns_without_jobs, :available_columns
|
alias_method :available_columns_without_jobs, :available_columns
|
||||||
alias_method :available_columns, :available_columns_with_jobs
|
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
|
end
|
||||||
|
|
||||||
module InstanceMethods
|
module InstanceMethods
|
||||||
|
@ -25,7 +28,7 @@ module TimeEntryQueryPatch
|
||||||
def available_columns_with_jobs
|
def available_columns_with_jobs
|
||||||
if @available_columns.nil?
|
if @available_columns.nil?
|
||||||
@available_columns = available_columns_without_jobs
|
@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
|
else
|
||||||
available_columns_without_jobs
|
available_columns_without_jobs
|
||||||
end
|
end
|
||||||
|
@ -48,5 +51,16 @@ module TimeEntryQueryPatch
|
||||||
[job.name, job.id.to_s]
|
[job.name, job.id.to_s]
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue