Compare commits
28 commits
v1.16.1
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
|
8af4d4d148 | ||
|
39949606e5 | ||
|
0e74472983 | ||
|
9ba33c9b71 | ||
975ab31440 | |||
|
8e6fcddd49 | ||
|
6b7d96ecb9 | ||
|
819fb5f530 | ||
f4c34d8e4e | |||
825387cc6f | |||
9d48c1ac6b | |||
c5f6808e6b | |||
f6a412f9be | |||
d89f9ac65b | |||
157af0c2c3 | |||
fa6aa24e0d | |||
|
548dfe22cc | ||
|
1f955f55a6 | ||
|
f04956cdcb | ||
|
9585515565 | ||
|
cb215e54eb | ||
|
7184248ccd | ||
|
bbf2566352 | ||
|
7c41c32dfd | ||
|
49bfdff822 | ||
|
81e6db24ef | ||
|
faf95f842e | ||
|
a2e021a6c6 |
10 changed files with 199 additions and 23 deletions
21
.gitignore
vendored
21
.gitignore
vendored
|
@ -1,11 +1,32 @@
|
||||||
|
# Ignore bundler config
|
||||||
|
/.bundle
|
||||||
|
|
||||||
|
# Ignore bundler installation
|
||||||
|
/vendor/bundle
|
||||||
|
|
||||||
|
# Ignore SASS and YARD cache
|
||||||
.sass-cache
|
.sass-cache
|
||||||
.yardoc
|
.yardoc
|
||||||
|
|
||||||
|
# Ignore the local configuration
|
||||||
config.yaml
|
config.yaml
|
||||||
htpasswd
|
htpasswd
|
||||||
|
|
||||||
|
# Ignore the SQLite databases
|
||||||
db/*
|
db/*
|
||||||
|
|
||||||
|
# Ignore generated documentation
|
||||||
doc/*
|
doc/*
|
||||||
|
|
||||||
|
# Ignore generated invoice assets
|
||||||
public/invoices/*
|
public/invoices/*
|
||||||
|
|
||||||
|
# Ignore compiled asssets
|
||||||
public/stylesheets/style.css
|
public/stylesheets/style.css
|
||||||
public/stylesheets/style.css.map
|
public/stylesheets/style.css.map
|
||||||
|
|
||||||
|
# Ignore custom invoice templates
|
||||||
templates/*_invoice.tex.erb
|
templates/*_invoice.tex.erb
|
||||||
|
|
||||||
|
# Ignore temporary files
|
||||||
tmp/*
|
tmp/*
|
||||||
|
|
1
.ruby-version
Normal file
1
.ruby-version
Normal file
|
@ -0,0 +1 @@
|
||||||
|
2.5.1
|
|
@ -1,5 +1,24 @@
|
||||||
= Stop… Camping Time! release news
|
= Stop… Camping Time! release news
|
||||||
|
|
||||||
|
== 1.17.1
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
* Fix the number/currency input to allow for all values ≥ 0.00
|
||||||
|
|
||||||
|
== 1.17.0
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* Add support for choosing what date/time is used for new entries
|
||||||
|
* Some textual and style tweaks
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fix crash when showing all entries in the timeline [#89c2a1]
|
||||||
|
|
||||||
|
Other bugfixes:
|
||||||
|
* Fix check that determines if last company info is used
|
||||||
|
|
||||||
== 1.16.1
|
== 1.16.1
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
18
Gemfile
Normal file
18
Gemfile
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
source "http://rubygems.org"
|
||||||
|
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||||
|
|
||||||
|
|
||||||
|
# This application is build on Camping, use it from our own Git master
|
||||||
|
gem "camping", github: "paulvt/camping", branch: "master"
|
||||||
|
|
||||||
|
# Use ActionView for the templates.
|
||||||
|
gem "actionview", "~> 4.2.10"
|
||||||
|
|
||||||
|
# Use ActiveRecord as the ORM.
|
||||||
|
gem "activerecord", "~> 4.2.10"
|
||||||
|
|
||||||
|
# Use SASS for handling CSS assets/Bootstrap.
|
||||||
|
gem "sass", "~> 3.4.6"
|
||||||
|
|
||||||
|
# Use SQLite as the database.
|
||||||
|
gem "sqlite3", "~> 1.3.9"
|
74
Gemfile.lock
Normal file
74
Gemfile.lock
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/paulvt/camping.git
|
||||||
|
revision: e6834563b244e0f24af2034d679cd6e8e9d932c0
|
||||||
|
branch: master
|
||||||
|
specs:
|
||||||
|
camping (2.1.602)
|
||||||
|
mab (>= 0.0.3)
|
||||||
|
rack (>= 1.0)
|
||||||
|
|
||||||
|
GEM
|
||||||
|
remote: http://rubygems.org/
|
||||||
|
specs:
|
||||||
|
actionview (4.2.11.3)
|
||||||
|
activesupport (= 4.2.11.3)
|
||||||
|
builder (~> 3.1)
|
||||||
|
erubis (~> 2.7.0)
|
||||||
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||||
|
activemodel (4.2.11.3)
|
||||||
|
activesupport (= 4.2.11.3)
|
||||||
|
builder (~> 3.1)
|
||||||
|
activerecord (4.2.11.3)
|
||||||
|
activemodel (= 4.2.11.3)
|
||||||
|
activesupport (= 4.2.11.3)
|
||||||
|
arel (~> 6.0)
|
||||||
|
activesupport (4.2.11.3)
|
||||||
|
i18n (~> 0.7)
|
||||||
|
minitest (~> 5.1)
|
||||||
|
thread_safe (~> 0.3, >= 0.3.4)
|
||||||
|
tzinfo (~> 1.1)
|
||||||
|
arel (6.0.4)
|
||||||
|
builder (3.2.4)
|
||||||
|
concurrent-ruby (1.1.9)
|
||||||
|
crass (1.0.6)
|
||||||
|
erubis (2.7.0)
|
||||||
|
i18n (0.9.5)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
loofah (2.12.0)
|
||||||
|
crass (~> 1.0.2)
|
||||||
|
nokogiri (>= 1.5.9)
|
||||||
|
mab (0.0.3)
|
||||||
|
mini_portile2 (2.6.1)
|
||||||
|
minitest (5.14.4)
|
||||||
|
nokogiri (1.12.5)
|
||||||
|
mini_portile2 (~> 2.6.1)
|
||||||
|
racc (~> 1.4)
|
||||||
|
racc (1.5.2)
|
||||||
|
rack (2.2.3)
|
||||||
|
rails-deprecated_sanitizer (1.0.4)
|
||||||
|
activesupport (>= 4.2.0.alpha)
|
||||||
|
rails-dom-testing (1.0.9)
|
||||||
|
activesupport (>= 4.2.0, < 5.0)
|
||||||
|
nokogiri (~> 1.6)
|
||||||
|
rails-deprecated_sanitizer (>= 1.0.1)
|
||||||
|
rails-html-sanitizer (1.4.2)
|
||||||
|
loofah (~> 2.3)
|
||||||
|
sass (3.4.25)
|
||||||
|
sqlite3 (1.3.13)
|
||||||
|
thread_safe (0.3.6)
|
||||||
|
tzinfo (1.2.9)
|
||||||
|
thread_safe (~> 0.1)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
actionview (~> 4.2.10)
|
||||||
|
activerecord (~> 4.2.10)
|
||||||
|
camping!
|
||||||
|
sass (~> 3.4.6)
|
||||||
|
sqlite3 (~> 1.3.9)
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
1.17.3
|
|
@ -39,6 +39,9 @@ and the following LaTeX programs:
|
||||||
* isodoc package (>= 1.00)
|
* isodoc package (>= 1.00)
|
||||||
* rubber
|
* rubber
|
||||||
|
|
||||||
|
It is also possible to use Bundler (which is the default when using
|
||||||
|
@config.ru@), in this you only need Ruby and Bundler installed.
|
||||||
|
|
||||||
== Installation
|
== Installation
|
||||||
|
|
||||||
For now, Stop… Camping Time! is in a developing state and not ready for
|
For now, Stop… Camping Time! is in a developing state and not ready for
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#!/usr/bin/env rackup
|
#!/usr/bin/env rackup
|
||||||
|
|
||||||
|
require "bundler/setup"
|
||||||
require "./stoptime"
|
require "./stoptime"
|
||||||
|
|
||||||
StopTime::Models::Base.establish_connection( :adapter => 'sqlite3',
|
StopTime::Models::Base.establish_connection(adapter: "sqlite3",
|
||||||
:database => 'db/stoptime.db',
|
database: "db/stoptime.db",
|
||||||
:timeout => 10000 )
|
timeout: 10000)
|
||||||
StopTime.create
|
StopTime.create
|
||||||
run StopTime
|
run StopTime
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
# Time resolution in minutes
|
# Time resolution in minutes
|
||||||
#time_resolution: 1
|
#time_resolution: 1
|
||||||
|
|
||||||
|
# Which date to use for new entries
|
||||||
|
# Supported values are: previous, today, none
|
||||||
|
#date_new_entry: today
|
||||||
|
|
||||||
# The default hourly rate
|
# The default hourly rate
|
||||||
#hourly_rate: 20.0
|
#hourly_rate: 20.0
|
||||||
|
|
||||||
|
|
71
stoptime.rb
71
stoptime.rb
|
@ -55,7 +55,7 @@ end
|
||||||
module StopTime
|
module StopTime
|
||||||
|
|
||||||
# The version of the application
|
# The version of the application
|
||||||
VERSION = '1.16.1'
|
VERSION = '1.17.1'
|
||||||
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.
|
||||||
|
@ -166,6 +166,7 @@ module StopTime::Models
|
||||||
"invoice_template" => "invoice",
|
"invoice_template" => "invoice",
|
||||||
"hourly_rate" => 20.0,
|
"hourly_rate" => 20.0,
|
||||||
"time_resolution" => 1,
|
"time_resolution" => 1,
|
||||||
|
"date_new_entry" => "today",
|
||||||
"vat_rate" => 21.0 }
|
"vat_rate" => 21.0 }
|
||||||
|
|
||||||
# Creates a new configuration object and loads the configuation.
|
# Creates a new configuration object and loads the configuation.
|
||||||
|
@ -888,6 +889,30 @@ module StopTime::Models
|
||||||
|
|
||||||
end # StopTime::Models
|
end # StopTime::Models
|
||||||
|
|
||||||
|
# = The Stop… Camping Time! helpers
|
||||||
|
module StopTime::Helpers
|
||||||
|
|
||||||
|
# Returns the date/time to use for new time entry defaults, or +nil+ if
|
||||||
|
# none is to be used. This method can use the last time entry (if any
|
||||||
|
# and if so configured). The result is based on the +date_new_entry+
|
||||||
|
# configuration option.
|
||||||
|
#
|
||||||
|
# @param last_entry [DateTime] the last time entry to use if configured
|
||||||
|
# for "previous"
|
||||||
|
# @return [DateTime, nil] the date/time to be used for new entry defaults
|
||||||
|
def date_time_new_entry(last_entry = nil)
|
||||||
|
case @config["date_new_entry"]
|
||||||
|
when "previous"
|
||||||
|
TimeEntry.last.end
|
||||||
|
when "today"
|
||||||
|
DateTime.now
|
||||||
|
when "none"
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
# = The Stop… Camping Time! controllers
|
# = The Stop… Camping Time! controllers
|
||||||
module StopTime::Controllers
|
module StopTime::Controllers
|
||||||
|
|
||||||
|
@ -1072,7 +1097,7 @@ module StopTime::Controllers
|
||||||
return render :customer_form
|
return render :customer_form
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
redirect R(Customers)
|
redirect R(CustomersN, customer_id)
|
||||||
end
|
end
|
||||||
end # class StopTime::Controllers::CustomersN
|
end # class StopTime::Controllers::CustomersN
|
||||||
|
|
||||||
|
@ -1281,7 +1306,7 @@ module StopTime::Controllers
|
||||||
tasks.each_key do |task|
|
tasks.each_key do |task|
|
||||||
# Create a new (billed) task clone that contains the selected time
|
# Create a new (billed) task clone that contains the selected time
|
||||||
# entries, leave the rest unbilled and associated with their task.
|
# entries, leave the rest unbilled and associated with their task.
|
||||||
bill_task = task.dup # FIXME: depends on rails version!
|
bill_task = task.dup
|
||||||
task.time_entries = task.time_entries - tasks[task]
|
task.time_entries = task.time_entries - tasks[task]
|
||||||
task.save
|
task.save
|
||||||
bill_task.time_entries = tasks[task]
|
bill_task.time_entries = tasks[task]
|
||||||
|
@ -1495,6 +1520,7 @@ module StopTime::Controllers
|
||||||
.where("stoptime_tasks.invoice_id" => nil)\
|
.where("stoptime_tasks.invoice_id" => nil)\
|
||||||
.order("start DESC")
|
.order("start DESC")
|
||||||
end
|
end
|
||||||
|
@time_entries = @time_entries.where.not(task_id: nil)
|
||||||
@time_entries.each do |te|
|
@time_entries.each do |te|
|
||||||
@input["bill_#{te.id}"] = true if te.bill?
|
@input["bill_#{te.id}"] = true if te.bill?
|
||||||
end
|
end
|
||||||
|
@ -1550,9 +1576,11 @@ module StopTime::Controllers
|
||||||
@task_list[t.customer.shortest_name] << [t.id, t.name]
|
@task_list[t.customer.shortest_name] << [t.id, t.name]
|
||||||
end
|
end
|
||||||
@input["bill"] = true
|
@input["bill"] = true
|
||||||
@input["date"] = DateTime.now.to_date
|
date_time_new = date_time_new_entry(TimeEntry.last)
|
||||||
@input["start"] = Time.now.to_formatted_s(:time_only)
|
if date_time_new
|
||||||
|
@input["date"] = date_time_new.to_date.to_formatted_s
|
||||||
|
@input["start"] = date_time_new.to_formatted_s(:time_only)
|
||||||
|
end
|
||||||
@target = [Timeline]
|
@target = [Timeline]
|
||||||
@button = "enter"
|
@button = "enter"
|
||||||
render :time_entry_form
|
render :time_entry_form
|
||||||
|
@ -1700,9 +1728,9 @@ module StopTime::Controllers
|
||||||
# If we are editing the current info and it is already associated
|
# If we are editing the current info and it is already associated
|
||||||
# with some invoices, create a new revision.
|
# with some invoices, create a new revision.
|
||||||
@history_warn = true if @company != CompanyInfo.last
|
@history_warn = true if @company != CompanyInfo.last
|
||||||
if @company == CompanyInfo.last and @company.invoices.length > 0
|
if @company.id == CompanyInfo.last.id and @company.invoices.length > 0
|
||||||
old_company = @company
|
old_company = @company
|
||||||
@company = old_company.clone # FIXME: depends on rails version!
|
@company = old_company.dup
|
||||||
@company.original = old_company
|
@company.original = old_company
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1834,7 +1862,7 @@ module StopTime::Views
|
||||||
elsif @active_tasks[customer].empty?
|
elsif @active_tasks[customer].empty?
|
||||||
p do
|
p do
|
||||||
em "No active projects/tasks found! " +
|
em "No active projects/tasks found! " +
|
||||||
"Register time on one of these tasks: "
|
"Register time on one of these projects/tasks: "
|
||||||
br
|
br
|
||||||
@tasks[customer].each do |task|
|
@tasks[customer].each do |task|
|
||||||
a task.name, href: R(CustomersNTasksN, customer.id, task.id)
|
a task.name, href: R(CustomersNTasksN, customer.id, task.id)
|
||||||
|
@ -2133,9 +2161,11 @@ module StopTime::Views
|
||||||
control_class: "col-sm-3 col-xs-4")
|
control_class: "col-sm-3 col-xs-4")
|
||||||
_form_input_with_label("Financial contact", "financial_contact", :text,
|
_form_input_with_label("Financial contact", "financial_contact", :text,
|
||||||
control_class: "col-sm-6 col-xs-8")
|
control_class: "col-sm-6 col-xs-8")
|
||||||
_form_input_with_label("Default hourly rate", "hourly_rate", :text,
|
_form_input_with_label("Default hourly rate", "hourly_rate", :number,
|
||||||
control_class: "col-sm-4 col-xs-5",
|
control_class: "col-sm-4 col-xs-5",
|
||||||
input_addon: "€ / h")
|
input_addon: "€ / h",
|
||||||
|
min: "0.00",
|
||||||
|
step: "0.01")
|
||||||
div.form_group do
|
div.form_group do
|
||||||
label.control_label.col_sm_3.col_xs_4 "Time specifications?"
|
label.control_label.col_sm_3.col_xs_4 "Time specifications?"
|
||||||
div.col_sm_6.col_xs_8 do
|
div.col_sm_6.col_xs_8 do
|
||||||
|
@ -2289,14 +2319,16 @@ module StopTime::Views
|
||||||
end if @task.billed?
|
end if @task.billed?
|
||||||
div.row do
|
div.row do
|
||||||
div.col_md_6.col_xs_12 do
|
div.col_md_6.col_xs_12 do
|
||||||
form.form_horizontal.form_condensed action: R(*@target), method: :post do
|
form.form_horizontal.form_condensed action: R(*@target),
|
||||||
|
method: :post do
|
||||||
div.form_group do
|
div.form_group do
|
||||||
label.control_label.col_sm_3.col_xs_4 "Customer", for: "customer"
|
label.control_label.col_sm_3.col_xs_4 "Customer", for: "customer"
|
||||||
div.col_sm_4.col_xs_8 do
|
div.col_sm_4.col_xs_8 do
|
||||||
_form_select("customer", @customer_list)
|
_form_select("customer", @customer_list)
|
||||||
end
|
end
|
||||||
div.col_sm_offset_2.col_sm_3.hidden_xs do
|
div.col_sm_offset_2.col_sm_3.hidden_xs do
|
||||||
a.btn.btn_default role: "button", href: R(CustomersN, @customer.id) do
|
a.btn.btn_default role: "button",
|
||||||
|
href: R(CustomersN, @customer.id) do
|
||||||
_icon("user")
|
_icon("user")
|
||||||
span "Show customer"
|
span "Show customer"
|
||||||
end
|
end
|
||||||
|
@ -2311,7 +2343,8 @@ module StopTime::Views
|
||||||
_form_input_radio("type", "hourly_rate", true)
|
_form_input_radio("type", "hourly_rate", true)
|
||||||
text!("Hourly rate: ")
|
text!("Hourly rate: ")
|
||||||
div.input_group do
|
div.input_group do
|
||||||
_form_input("hourly_rate", :number, "Hourly rate")
|
_form_input("hourly_rate", :number, "Hourly rate",
|
||||||
|
min: "0.00", step: "0.01")
|
||||||
span.input_group_addon "€ / h"
|
span.input_group_addon "€ / h"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2321,7 +2354,8 @@ module StopTime::Views
|
||||||
_form_input_radio("type", "fixed_cost")
|
_form_input_radio("type", "fixed_cost")
|
||||||
text!("Fixed cost: ")
|
text!("Fixed cost: ")
|
||||||
div.input_group do
|
div.input_group do
|
||||||
_form_input("fixed_cost", :number, "Fixed cost")
|
_form_input("fixed_cost", :number, "Fixed cost",
|
||||||
|
min: "0.00", step: "0.01")
|
||||||
span.input_group_addon "€"
|
span.input_group_addon "€"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2919,7 +2953,7 @@ module StopTime::Views
|
||||||
tbody do
|
tbody do
|
||||||
invoices.each do |invoice|
|
invoices.each do |invoice|
|
||||||
due_class = invoice.past_due? ? "warning" : ""
|
due_class = invoice.past_due? ? "warning" : ""
|
||||||
due_class = "error" if invoice.way_past_due?
|
due_class = "danger" if invoice.way_past_due?
|
||||||
tr(class: due_class) do
|
tr(class: due_class) do
|
||||||
td do
|
td do
|
||||||
a invoice.number,
|
a invoice.number,
|
||||||
|
@ -3123,6 +3157,7 @@ module StopTime::Views
|
||||||
# @param [Customer, nil] task a task to show time entries for
|
# @param [Customer, nil] task a task to show time entries for
|
||||||
# @return [Mab::Mixin::Tag] the main menu
|
# @return [Mab::Mixin::Tag] the main menu
|
||||||
def _time_entries(customer=nil, task=nil)
|
def _time_entries(customer=nil, task=nil)
|
||||||
|
date_time_new = date_time_new_entry(@time_entries.first)
|
||||||
form.form_inline action: R(Timeline), method: :post do
|
form.form_inline action: R(Timeline), method: :post do
|
||||||
table.table.table_condensed.table_striped.table_hover do
|
table.table.table_condensed.table_striped.table_hover do
|
||||||
thead do
|
thead do
|
||||||
|
@ -3156,11 +3191,11 @@ module StopTime::Views
|
||||||
end
|
end
|
||||||
td.col_md_1 do
|
td.col_md_1 do
|
||||||
input.form_control type: :text, name: "date",
|
input.form_control type: :text, name: "date",
|
||||||
value: DateTime.now.to_date.to_formatted_s
|
value: date_time_new && date_time_new.to_date.to_formatted_s
|
||||||
end
|
end
|
||||||
td.col_md_1 do
|
td.col_md_1 do
|
||||||
input.form_control type: :text, name: "start",
|
input.form_control type: :text, name: "start",
|
||||||
value: DateTime.now.to_time.to_formatted_s(:time_only)
|
value: date_time_new && date_time_new.to_formatted_s(:time_only)
|
||||||
end
|
end
|
||||||
td.col_md_1 do
|
td.col_md_1 do
|
||||||
input.form_control type: :text, name: "end"
|
input.form_control type: :text, name: "end"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* CSS file for Stop… Camping Time! */
|
/* CSS file for Stop... Camping Time! */
|
||||||
|
|
||||||
/* Main elements */
|
/* Main elements */
|
||||||
html
|
html
|
||||||
|
|
Loading…
Reference in a new issue