Merge branch 'release/1.8'
This commit is contained in:
commit
32d0e48b1d
|
@ -2,8 +2,7 @@
|
|||
config.yaml
|
||||
htpasswd
|
||||
db/*
|
||||
public/invoices/*.pdf
|
||||
public/invoices/*.tex
|
||||
public/invoices/*
|
||||
public/stylesheets/style.css
|
||||
templates/*_invoice.tex.erb
|
||||
tmp/*
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
= Stop… Camping Time! release news
|
||||
|
||||
== 1.8
|
||||
|
||||
Features:
|
||||
|
||||
* Round start and end time of time entries to a given time
|
||||
resolution [#9f7883]
|
||||
* Add goto buttons in the time entry view
|
||||
* Add "Create a new invoice" per customer in the invoices list view [#50f182]
|
||||
* Add "Remove old" button to remove an old invoice (and thus regenerate it) [#4235ef]
|
||||
|
||||
Other bugfixes:
|
||||
* Fix bug preventing any updats to tasks with AR 4
|
||||
* Fix invoid period calculation
|
||||
|
||||
== 1.6
|
||||
|
||||
Application:
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# Default configuration for Stop… Camping Time!
|
||||
|
||||
# Time resolution in minutes
|
||||
#time_resolution: 1
|
||||
|
||||
# The default hourly rate
|
||||
#hourly_rate: 20.0
|
||||
|
||||
|
|
98
stoptime.rb
98
stoptime.rb
|
@ -54,7 +54,7 @@ end
|
|||
module StopTime
|
||||
|
||||
# The version of the application
|
||||
VERSION = '1.6'
|
||||
VERSION = '1.8'
|
||||
puts "Starting Stop… Camping Time! version #{VERSION}"
|
||||
|
||||
# The parsed configuration (Hash).
|
||||
|
@ -144,6 +144,7 @@ module StopTime::Models
|
|||
DefaultConfig = { "invoice_id" => "%Y%N",
|
||||
"invoice_template" => "invoice",
|
||||
"hourly_rate" => 20.0,
|
||||
"time_resolution" => 1,
|
||||
"vat_rate" => 21.0 }
|
||||
|
||||
# Creates a new configuration object and loads the configuation.
|
||||
|
@ -348,11 +349,36 @@ module StopTime::Models
|
|||
belongs_to :task
|
||||
has_one :customer, :through => :task
|
||||
|
||||
before_validation :round_start_end
|
||||
|
||||
# Returns the total amount of time, the duration, in hours (up to
|
||||
# 2 decimals only!).
|
||||
def hours_total
|
||||
((self.end - self.start) / 1.hour).round(2)
|
||||
end
|
||||
|
||||
#########
|
||||
protected
|
||||
|
||||
def round_start_end
|
||||
self.start = round_time(self.start)
|
||||
self.end = round_time(self.end)
|
||||
end
|
||||
|
||||
#######
|
||||
private
|
||||
|
||||
def round_time(t)
|
||||
config = Config.instance
|
||||
res = config["time_resolution"]
|
||||
down = t - (t.to_i % res.minutes)
|
||||
up = down + res.minutes
|
||||
|
||||
diff_down = t - down
|
||||
diff_up = up - t
|
||||
|
||||
diff_down < diff_up ? down : up
|
||||
end
|
||||
end
|
||||
|
||||
# == The invoice class
|
||||
|
@ -405,10 +431,9 @@ module StopTime::Models
|
|||
# Returns the invoice period based on the contained tasks (Array of Time).
|
||||
# See also Task#bill_period.
|
||||
def period
|
||||
# FIXME: maybe should be updated_at?
|
||||
p = [created_at, created_at]
|
||||
return p if tasks.empty?
|
||||
return [created_at, created_at] if tasks.empty?
|
||||
|
||||
p = [DateTime.now, DateTime.new(0)]
|
||||
tasks.each do |task|
|
||||
tp = task.bill_period
|
||||
p[0] = tp[0] if tp[0] < p[0]
|
||||
|
@ -990,7 +1015,7 @@ module StopTime::Controllers
|
|||
return redirect R(CustomersN, customer_id) if @input.cancel
|
||||
@task = Task.find(task_id)
|
||||
if @input.has_key? "update"
|
||||
@task["customer"] = Customer.find(@input["customer"])
|
||||
@task.customer = Customer.find(@input["customer"])
|
||||
@task.name = @input["name"] unless @input["name"].blank?
|
||||
if @task.billed? and @input["invoice_comment"].present?
|
||||
@task.invoice_comment = @input["invoice_comment"]
|
||||
|
@ -1135,15 +1160,16 @@ module StopTime::Controllers
|
|||
@vat = @invoice.vat_summary
|
||||
@period = @invoice.period
|
||||
|
||||
tex_file = PUBLIC_DIR + "invoices/#{@number}.tex"
|
||||
pdf_file = PUBLIC_DIR + "invoices/#{@number}.pdf"
|
||||
if @format == "html"
|
||||
@input = @invoice.attributes
|
||||
@invoice_file_present = tex_file.exist?
|
||||
render :invoice_form
|
||||
elsif @format == "tex"
|
||||
tex_file = PUBLIC_DIR + "invoices/#{@number}.tex"
|
||||
_generate_invoice_tex(@number) unless tex_file.exist?
|
||||
redirect R(Static, "") + "invoices/#{tex_file.basename}"
|
||||
elsif @format == "pdf"
|
||||
pdf_file = PUBLIC_DIR + "invoices/#{@number}.pdf"
|
||||
_generate_invoice_pdf(@number) unless pdf_file.exist?
|
||||
redirect R(Static, "") + "invoices/#{pdf_file.basename}"
|
||||
end
|
||||
|
@ -1160,6 +1186,21 @@ module StopTime::Controllers
|
|||
redirect R(CustomersNInvoicesX, customer_id, invoice_number)
|
||||
end
|
||||
|
||||
# Find the invoice with the given _invoice_number_ for the customer
|
||||
# with the given _customer_id_ and deletes existing invoice files.
|
||||
def delete(customer_id, invoice_number)
|
||||
@invoice = Invoice.find_by_number(@number)
|
||||
@customer = Customer.find(customer_id)
|
||||
|
||||
tex_file = PUBLIC_DIR + "invoices/#{invoice_number}.tex"
|
||||
File.unlink(tex_file) if tex_file.exist?
|
||||
|
||||
pdf_file = PUBLIC_DIR + "invoices/#{invoice_number}.pdf"
|
||||
File.unlink(pdf_file) if pdf_file.exist?
|
||||
|
||||
redirect R(CustomersNInvoicesX, customer_id, invoice_number)
|
||||
end
|
||||
|
||||
##############
|
||||
# Private helper methods
|
||||
#
|
||||
|
@ -1252,7 +1293,7 @@ module StopTime::Controllers
|
|||
render :time_entries
|
||||
end
|
||||
|
||||
# Registers a time entry and redirects to Timeline.
|
||||
# Registers a time entry and redirects back to the referer.
|
||||
# If the provided information was invalid, the errors are retrieved.
|
||||
def post
|
||||
if @input.has_key? "enter"
|
||||
|
@ -1377,7 +1418,7 @@ module StopTime::Controllers
|
|||
Customer.all.each do |customer|
|
||||
invoices = customer.invoices
|
||||
next if invoices.empty?
|
||||
@invoices[customer.name] = invoices
|
||||
@invoices[customer] = invoices
|
||||
invoices.each do |i|
|
||||
@input["paid_#{i.number}"] = true if i.paid?
|
||||
end
|
||||
|
@ -1669,7 +1710,7 @@ module StopTime::Views
|
|||
td { a entry.comment, :href => R(TimelineN, entry.id),
|
||||
:title => entry.comment }
|
||||
else
|
||||
td { a(:href => R(TimelineN, entry.id)){ i "None" } }
|
||||
td { a(:href => R(TimelineN, entry.id)) { i "None" } }
|
||||
end
|
||||
td { "%.2fh" % entry.hours_total }
|
||||
td do
|
||||
|
@ -1705,12 +1746,16 @@ module StopTime::Views
|
|||
label.control_label "Customer", :for => "customer"
|
||||
div.controls do
|
||||
_form_select("customer", @customer_list)
|
||||
a.btn "» Go to customer", :href => R(CustomersN, @time_entry.customer.id)
|
||||
end
|
||||
end
|
||||
div.control_group do
|
||||
label.control_label "Task", :for => "task"
|
||||
label.control_label "Project/Task", :for => "task"
|
||||
div.controls do
|
||||
_form_select_nested("task", @task_list)
|
||||
a.btn "» Go to project/task", :href => R(CustomersNTasksN,
|
||||
@time_entry.customer.id,
|
||||
@time_entry.task.id)
|
||||
end
|
||||
end
|
||||
if @time_entry.present? and @time_entry.task.billed?
|
||||
|
@ -2020,10 +2065,16 @@ module StopTime::Views
|
|||
else
|
||||
div.row do
|
||||
div.span7 do
|
||||
@invoices.keys.sort.each do |key|
|
||||
next if @invoices[key].empty?
|
||||
h3 { key }
|
||||
_invoice_list(@invoices[key])
|
||||
@invoices.keys.sort.each do |customer|
|
||||
next if @invoices[customer].empty?
|
||||
h2 do
|
||||
text! customer.name
|
||||
div.btn_group.pull_right do
|
||||
a.btn.btn_small "» Create a new invoice",
|
||||
:href => R(CustomersNInvoicesNew, customer.id)
|
||||
end
|
||||
end
|
||||
_invoice_list(@invoices[customer])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2121,11 +2172,15 @@ module StopTime::Views
|
|||
task.time_entries.each do |entry|
|
||||
tr do
|
||||
td.indent do
|
||||
time_spec = "from #{entry.start.to_formatted_s(:time_only)} " +
|
||||
"until #{entry.end.to_formatted_s(:time_only)} " +
|
||||
"on #{entry.date.to_date}"
|
||||
if entry.comment.present?
|
||||
a "• #{entry.comment}", :href => R(TimelineN, entry.id),
|
||||
:title => entry.comment
|
||||
:title => "#{entry.comment} (#{time_spec})"
|
||||
else
|
||||
a :href => R(TimelineN, entry.id) { i "• None" }
|
||||
a(:href => R(TimelineN, entry.id),
|
||||
:title => time_spec) { i "• None" }
|
||||
end
|
||||
end
|
||||
td.text_right { "%.2fh" % entry.hours_total }
|
||||
|
@ -2169,6 +2224,15 @@ module StopTime::Views
|
|||
a.btn "» View company info",
|
||||
:href => R(Company, :revision => @company.revision)
|
||||
end
|
||||
|
||||
div.alert.alert_danger do
|
||||
form.form_inline :action => R(CustomersNInvoicesX,
|
||||
@customer.id, @invoice.number),
|
||||
:method => :delete do
|
||||
button.btn.btn_danger "» Remove old", :type => "submit"
|
||||
text! "An invoice has already been generated!"
|
||||
end
|
||||
end if @invoice_file_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
.text-right
|
||||
text-align: right !important
|
||||
|
||||
.btn-group
|
||||
margin-bottom: 8px
|
||||
|
||||
/* Main elements */
|
||||
body
|
||||
padding-top: 40px
|
||||
|
|
Loading…
Reference in New Issue