Merge branch 'release/1.14.0'
This commit is contained in:
commit
bcabb15f53
|
@ -1,5 +1,13 @@
|
||||||
= Stop… Camping Time! release news
|
= Stop… Camping Time! release news
|
||||||
|
|
||||||
|
== 1.14.0
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Show customer specific time entries in the customer form [#08650a]
|
||||||
|
* Also allow for registering time for the customer
|
||||||
|
* Show a grand total of unbilled hours and amount in the overview
|
||||||
|
|
||||||
== 1.12.1
|
== 1.12.1
|
||||||
|
|
||||||
Small update release to include to missing 1.12 change log and added
|
Small update release to include to missing 1.12 change log and added
|
||||||
|
|
268
stoptime.rb
268
stoptime.rb
|
@ -54,7 +54,7 @@ end
|
||||||
module StopTime
|
module StopTime
|
||||||
|
|
||||||
# The version of the application
|
# The version of the application
|
||||||
VERSION = '1.12.1'
|
VERSION = '1.14.0'
|
||||||
puts "Starting Stop… Camping Time! version #{VERSION}"
|
puts "Starting Stop… Camping Time! version #{VERSION}"
|
||||||
|
|
||||||
# @return [Hash{String=>Object}] The parsed configuration.
|
# @return [Hash{String=>Object}] The parsed configuration.
|
||||||
|
@ -898,6 +898,7 @@ module StopTime::Controllers
|
||||||
@task_count = 0
|
@task_count = 0
|
||||||
@active_tasks = {}
|
@active_tasks = {}
|
||||||
@active_tasks_summary = {}
|
@active_tasks_summary = {}
|
||||||
|
@totals = [0.0, 0,0]
|
||||||
Customer.all.each do |customer|
|
Customer.all.each do |customer|
|
||||||
tasks = customer.unbilled_tasks
|
tasks = customer.unbilled_tasks
|
||||||
@tasks[customer] = tasks
|
@tasks[customer] = tasks
|
||||||
|
@ -908,7 +909,9 @@ module StopTime::Controllers
|
||||||
active_tasks.inject([0.0, 0.0]) do |summ, task|
|
active_tasks.inject([0.0, 0.0]) do |summ, task|
|
||||||
task_summ = task.summary
|
task_summ = task.summary
|
||||||
summ[0] += task_summ[0]
|
summ[0] += task_summ[0]
|
||||||
|
@totals[0] += task_summ[0]
|
||||||
summ[1] += task_summ[2]
|
summ[1] += task_summ[2]
|
||||||
|
@totals[1] += task_summ[2]
|
||||||
summ
|
summ
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1014,10 +1017,17 @@ module StopTime::Controllers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@time_entries = @customer.time_entries.order("start DESC")
|
||||||
@invoices = @customer.invoices
|
@invoices = @customer.invoices
|
||||||
@invoices.each do |i|
|
@invoices.each do |i|
|
||||||
@input["paid_#{i.number}"] = true if i.paid?
|
@input["paid_#{i.number}"] = true if i.paid?
|
||||||
end
|
end
|
||||||
|
@task_list = Hash.new { |h, k| h[k] = Array.new }
|
||||||
|
@customer.tasks.reject { |t| t.billed? }.each do |t|
|
||||||
|
@task_list[t.customer.shortest_name] << [t.id, t.name]
|
||||||
|
end
|
||||||
|
@input["bill"] = true # Bill by default.
|
||||||
|
@input["task"] = @time_entries.first.task.id if @time_entries.present?
|
||||||
|
|
||||||
@target = [CustomersN, @customer.id]
|
@target = [CustomersN, @customer.id]
|
||||||
@button = "update"
|
@button = "update"
|
||||||
|
@ -1476,10 +1486,10 @@ module StopTime::Controllers
|
||||||
# also for quickly registering time.
|
# also for quickly registering time.
|
||||||
#
|
#
|
||||||
# path:: +/timeline+
|
# path:: +/timeline+
|
||||||
# view:: {Views#time_entries}
|
# view:: {Views#timeline}
|
||||||
class Timeline
|
class Timeline
|
||||||
# Retrieves all registered time in descending order to present
|
# Retrieves all registered time in descending order to present
|
||||||
# the timeline using {Views#time_entries}.
|
# the timeline using {Views#timeline}.
|
||||||
def get
|
def get
|
||||||
if @input["show"] == "all"
|
if @input["show"] == "all"
|
||||||
@time_entries = TimeEntry.order("start DESC")
|
@time_entries = TimeEntry.order("start DESC")
|
||||||
|
@ -1498,7 +1508,7 @@ module StopTime::Controllers
|
||||||
end
|
end
|
||||||
@input["bill"] = true # Bill by default.
|
@input["bill"] = true # Bill by default.
|
||||||
@input["task"] = @time_entries.first.task.id if @time_entries.present?
|
@input["task"] = @time_entries.first.task.id if @time_entries.present?
|
||||||
render :time_entries
|
render :timeline
|
||||||
end
|
end
|
||||||
|
|
||||||
# Registers a time entry and redirects back to the referer.
|
# Registers a time entry and redirects back to the referer.
|
||||||
|
@ -1854,122 +1864,44 @@ module StopTime::Views
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
div.row do
|
||||||
|
div.span12 do
|
||||||
|
table.table.table_condensed.grand_total do
|
||||||
|
col
|
||||||
|
col.total_hours
|
||||||
|
col.total_amount
|
||||||
|
tr do
|
||||||
|
td { big { b "Grand total" } }
|
||||||
|
td.text_right { big { b("%.2fh" % @totals[0]) } }
|
||||||
|
td.text_right { big { b("€ %.2f" % @totals[1]) } }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The main overview showing the timeline of registered time.
|
# The main overview showing the timeline of registered time.
|
||||||
# If a task ID is given as an argument, the task column will be hidden
|
|
||||||
# and it will be assumed it is used as a partial view.
|
|
||||||
#
|
#
|
||||||
# FIXME: This should be done in a nicer way.
|
|
||||||
#
|
|
||||||
# @param [Fixnum, nil] task_id ID of a task
|
|
||||||
# @return [Mab::Mixin::Tag] the main timeline (time entry list) overview
|
# @return [Mab::Mixin::Tag] the main timeline (time entry list) overview
|
||||||
def time_entries(task_id=nil)
|
def timeline
|
||||||
if task_id.present?
|
header.page_header do
|
||||||
h2 "Registered #{@task.billed? ? "billed" : "unbilled"} time"
|
h1 do
|
||||||
else
|
text! "Timeline"
|
||||||
header.page_header do
|
small "#{@time_entries.count} time entries"
|
||||||
h1 do
|
div.btn_group.pull_right do
|
||||||
text! "Timeline"
|
a.btn.btn_small.dropdown_toggle :href => "#", "data-toggle" => "dropdown" do
|
||||||
small "#{@time_entries.count} time entries"
|
text! @input["show"] == "all" ? "All" : "Unbilled"
|
||||||
div.btn_group.pull_right do
|
span.caret
|
||||||
a.btn.btn_small.dropdown_toggle :href => "#", "data-toggle" => "dropdown" do
|
end
|
||||||
text! @input["show"] == "all" ? "All" : "Unbilled"
|
ul.dropdown_menu :role => "menu", :aria_labelledby => "dLabel" do
|
||||||
span.caret
|
li { a "All", :href => R(Timeline, :show => "all") }
|
||||||
end
|
li { a "Unbilled", :href => R(Timeline, :show => "unbilled") }
|
||||||
ul.dropdown_menu :role => "menu", :aria_labelledby => "dLabel" do
|
|
||||||
li { a "All", :href => R(Timeline, :show => "all") }
|
|
||||||
li { a "Unbilled", :href => R(Timeline, :show => "unbilled") }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
table.table.table_condensed.table_striped.table_hover do
|
|
||||||
unless task_id.present?
|
|
||||||
col.customer_short
|
|
||||||
col.task
|
|
||||||
end
|
|
||||||
col.date
|
|
||||||
col.start_time
|
|
||||||
col.end_time
|
|
||||||
col.comment
|
|
||||||
col.hours
|
|
||||||
col.flag
|
|
||||||
thead do
|
|
||||||
tr do
|
|
||||||
unless task_id.present?
|
|
||||||
th "Customer"
|
|
||||||
th "Project/Task"
|
|
||||||
end
|
|
||||||
th "Date"
|
|
||||||
th "Start"
|
|
||||||
th "End"
|
|
||||||
th "Comment"
|
|
||||||
th "Total"
|
|
||||||
th "Bill?"
|
|
||||||
th {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
tbody do
|
|
||||||
form.form_inline :action => R(Timeline), :method => :post do
|
|
||||||
tr do
|
|
||||||
if task_id.present?
|
|
||||||
input :type => :hidden, :name => "task", :value => task_id
|
|
||||||
else
|
|
||||||
td { }
|
|
||||||
td { _form_select_nested("task", @task_list, :class => "task") }
|
|
||||||
end
|
|
||||||
td { input.date :type => :text, :name => "date",
|
|
||||||
:value => DateTime.now.to_date.to_formatted_s }
|
|
||||||
td { input.start_time :type => :text, :name => "start",
|
|
||||||
:value => DateTime.now.to_time.to_formatted_s(:time_only) }
|
|
||||||
td { input.end_time :type => :text, :name => "end" }
|
|
||||||
td { input.comment :type => :text, :name => "comment" }
|
|
||||||
td { "N/A" }
|
|
||||||
td { _form_input_checkbox("bill") }
|
|
||||||
td do
|
|
||||||
button.btn.btn_small.btn_primary "Enter", :type => :submit, :name => "enter", :value => "Enter"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@time_entries.each do |entry|
|
|
||||||
tr(:class => entry.task.billed? ? "billed" : nil) do
|
|
||||||
unless task_id.present?
|
|
||||||
td do
|
|
||||||
a entry.customer.shortest_name,
|
|
||||||
:title => entry.customer.shortest_name,
|
|
||||||
:href => R(CustomersN, entry.customer.id)
|
|
||||||
end
|
|
||||||
td do
|
|
||||||
a entry.task.name,
|
|
||||||
:title => entry.task.name,
|
|
||||||
:href => R(CustomersNTasksN, entry.customer.id, entry.task.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
td { entry.date.to_date }
|
|
||||||
td { entry.start.to_formatted_s(:time_only) }
|
|
||||||
td { entry.end.to_formatted_s(:time_only)}
|
|
||||||
if entry.comment.present?
|
|
||||||
td { a entry.comment, :href => R(TimelineN, entry.id),
|
|
||||||
:title => entry.comment }
|
|
||||||
else
|
|
||||||
td { a(:href => R(TimelineN, entry.id)) { i "None" } }
|
|
||||||
end
|
|
||||||
td { "%.2fh" % entry.hours_total }
|
|
||||||
td do
|
|
||||||
i(:class => "icon-ok") if entry.bill?
|
|
||||||
end
|
|
||||||
td do
|
|
||||||
form.form_inline :action => R(TimelineN, entry.id), :method => :post do
|
|
||||||
button.btn.btn_mini.btn_danger "Delete", :type => :submit, :name => "delete", :value => "Delete"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
_time_entries
|
||||||
end
|
end
|
||||||
|
|
||||||
# Form for editing a time entry ({Models::TimeEntry}).
|
# Form for editing a time entry ({Models::TimeEntry}).
|
||||||
|
@ -2238,6 +2170,14 @@ module StopTime::Views
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
div.row do
|
||||||
|
div.span12 do
|
||||||
|
# Show registered time (ab)using the time_entries view as partial view.
|
||||||
|
h2 "Registered time"
|
||||||
|
_time_entries(@customer) unless @method == "create"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Form for updating the properties of a task ({Models::Task}).
|
# Form for updating the properties of a task ({Models::Task}).
|
||||||
|
@ -2299,7 +2239,8 @@ module StopTime::Views
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# Show registered time (ab)using the time_entries view as partial view.
|
# Show registered time (ab)using the time_entries view as partial view.
|
||||||
time_entries(@task.id) unless @method == "create"
|
h2 "Registered #{@task.billed? ? "billed" : "unbilled"} time"
|
||||||
|
_time_entries(@customer, @task) unless @method == "create"
|
||||||
end
|
end
|
||||||
|
|
||||||
# The main overview of the existing invoices.
|
# The main overview of the existing invoices.
|
||||||
|
@ -2918,4 +2859,109 @@ module StopTime::Views
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Partial view that shows time entries. If a customer is given,
|
||||||
|
# only time entries of that customer are shown, and if also a task is
|
||||||
|
# given, then only the time entries of that task are shown.
|
||||||
|
#
|
||||||
|
# @param [Customer, nil] customer an customer to show time entries for
|
||||||
|
# @param [Customer, nil] task a task to show time entries for
|
||||||
|
# @return [Mab::Mixin::Tag] the main menu
|
||||||
|
def _time_entries(customer=nil, task=nil)
|
||||||
|
table.table.table_condensed.table_striped.table_hover do
|
||||||
|
if customer.blank?
|
||||||
|
col.customer_short
|
||||||
|
end
|
||||||
|
if task.blank?
|
||||||
|
col.task
|
||||||
|
end
|
||||||
|
col.date
|
||||||
|
col.start_time
|
||||||
|
col.end_time
|
||||||
|
col.comment
|
||||||
|
col.hours
|
||||||
|
col.flag
|
||||||
|
thead do
|
||||||
|
tr do
|
||||||
|
if customer.blank?
|
||||||
|
th "Customer"
|
||||||
|
end
|
||||||
|
if task.blank?
|
||||||
|
th "Project/Task"
|
||||||
|
end
|
||||||
|
th "Date"
|
||||||
|
th "Start"
|
||||||
|
th "End"
|
||||||
|
th "Comment"
|
||||||
|
th "Total"
|
||||||
|
th "Bill?"
|
||||||
|
th {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tbody do
|
||||||
|
form.form_inline :action => R(Timeline), :method => :post do
|
||||||
|
tr do
|
||||||
|
if task.present?
|
||||||
|
input :type => :hidden, :name => "task", :value => task.id
|
||||||
|
else
|
||||||
|
if customer.blank?
|
||||||
|
td { }
|
||||||
|
end
|
||||||
|
td { _form_select_nested("task", @task_list, :class => "task") }
|
||||||
|
end
|
||||||
|
td { input.date :type => :text, :name => "date",
|
||||||
|
:value => DateTime.now.to_date.to_formatted_s }
|
||||||
|
td { input.start_time :type => :text, :name => "start",
|
||||||
|
:value => DateTime.now.to_time.to_formatted_s(:time_only) }
|
||||||
|
td { input.end_time :type => :text, :name => "end" }
|
||||||
|
td { input.comment :type => :text, :name => "comment" }
|
||||||
|
td { "N/A" }
|
||||||
|
td { _form_input_checkbox("bill") }
|
||||||
|
td do
|
||||||
|
button.btn.btn_small.btn_primary "Enter",
|
||||||
|
:type => :submit,
|
||||||
|
:name => "enter",
|
||||||
|
:value => "Enter"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@time_entries.each do |entry|
|
||||||
|
tr(:class => entry.task.billed? ? "billed" : nil) do
|
||||||
|
if customer.blank?
|
||||||
|
td do
|
||||||
|
a entry.customer.shortest_name,
|
||||||
|
:title => entry.customer.shortest_name,
|
||||||
|
:href => R(CustomersN, entry.customer.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if task.blank?
|
||||||
|
td do
|
||||||
|
a entry.task.name,
|
||||||
|
:title => entry.task.name,
|
||||||
|
:href => R(CustomersNTasksN, entry.customer.id, entry.task.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
td { entry.date.to_date }
|
||||||
|
td { entry.start.to_formatted_s(:time_only) }
|
||||||
|
td { entry.end.to_formatted_s(:time_only)}
|
||||||
|
if entry.comment.present?
|
||||||
|
td { a entry.comment, :href => R(TimelineN, entry.id),
|
||||||
|
:title => entry.comment }
|
||||||
|
else
|
||||||
|
td { a(:href => R(TimelineN, entry.id)) { i "None" } }
|
||||||
|
end
|
||||||
|
td { "%.2fh" % entry.hours_total }
|
||||||
|
td do
|
||||||
|
i(:class => "icon-ok") if entry.bill?
|
||||||
|
end
|
||||||
|
td do
|
||||||
|
form.form_inline :action => R(TimelineN, entry.id), :method => :post do
|
||||||
|
button.btn.btn_mini.btn_danger "Delete", :type => :submit, :name => "delete", :value => "Delete"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end # module StopTime::Views
|
end # module StopTime::Views
|
||||||
|
|
|
@ -48,6 +48,8 @@ table
|
||||||
|
|
||||||
col.amount, col.hours, col.hourly_rate
|
col.amount, col.hours, col.hourly_rate
|
||||||
width: 85px
|
width: 85px
|
||||||
|
col.total_hours, col.total_amount
|
||||||
|
width: 130px
|
||||||
col.reg_hours
|
col.reg_hours
|
||||||
width: 90px
|
width: 90px
|
||||||
col.flag
|
col.flag
|
||||||
|
@ -93,6 +95,9 @@ table
|
||||||
td.indent
|
td.indent
|
||||||
padding-left: 20px
|
padding-left: 20px
|
||||||
|
|
||||||
|
table.grand-total
|
||||||
|
margin-top: 30px
|
||||||
|
|
||||||
/* Form layout */
|
/* Form layout */
|
||||||
.form-condensed
|
.form-condensed
|
||||||
padding-top: 0px
|
padding-top: 0px
|
||||||
|
|
Loading…
Reference in New Issue