Pratt 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/Pratt.gemspec +38 -12
- data/README.html +85 -0
- data/README.txt +6 -3
- data/TODO +6 -3
- data/VERSION +1 -1
- data/config.rb +3 -17
- data/db/zips.csv.zip +0 -0
- data/lib/models.rb +8 -0
- data/lib/pratt.rb +58 -293
- data/lib/pratt.rb.orig +626 -0
- data/lib/pratt/core_ext.rb +6 -0
- data/lib/pratt/{array.rb → core_ext/array.rb} +5 -0
- data/lib/pratt/core_ext/float.rb +28 -0
- data/lib/pratt/core_ext/nil.rb +5 -0
- data/lib/pratt/core_ext/numeric.rb +9 -0
- data/lib/pratt/{string.rb → core_ext/string.rb} +13 -0
- data/lib/pratt/core_ext/time.rb +20 -0
- data/lib/pratt/dialogs.rb +81 -0
- data/lib/pratt/formatting.rb +24 -0
- data/lib/pratt/project_actions.rb +25 -0
- data/lib/pratt/reports.rb +127 -0
- data/models/app.rb +1 -2
- data/models/customer.rb +22 -0
- data/models/invoice.rb +28 -0
- data/models/invoice_whence.rb +18 -0
- data/models/payment.rb +1 -1
- data/models/pratt.rb +0 -9
- data/models/project.rb +16 -22
- data/models/whence.rb +10 -1
- data/models/zip.rb +27 -0
- data/spec/fixtures/graph.expectation +0 -5
- data/spec/fixtures/proportions.expectation +4 -0
- data/spec/float_spec.rb +24 -0
- data/spec/numeric_spec.rb +30 -0
- data/spec/pratt_spec.rb +5 -4
- data/spec/project_spec.rb +8 -2
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +31 -1
- data/spec/string_ext_spec.rb +33 -0
- data/views/current.eruby +5 -0
- data/views/general-invoice.eruby +538 -0
- data/views/graph.eruby +2 -7
- data/views/invoice.eruby +3 -3
- data/views/main.rb +8 -6
- data/views/proportions.eruby +4 -0
- data/views/raw.eruby +1 -1
- metadata +86 -33
- data/lib/pratt/time.rb +0 -12
- data/views/env.rb +0 -22
@@ -0,0 +1,18 @@
|
|
1
|
+
class InvoiceWhence < ActiveRecord::Base
|
2
|
+
belongs_to :invoice
|
3
|
+
belongs_to :whence
|
4
|
+
|
5
|
+
def self.migrate which = :up
|
6
|
+
ActiveRecord::Schema.define do
|
7
|
+
if which == :up
|
8
|
+
create_table :invoice_whences do |t|
|
9
|
+
t.references :invoice
|
10
|
+
t.references :whence
|
11
|
+
t.datetime :created_at
|
12
|
+
end
|
13
|
+
elsif which == :down
|
14
|
+
drop_table :invoice_whences
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/models/payment.rb
CHANGED
data/models/pratt.rb
CHANGED
@@ -6,14 +6,5 @@ class Pratt
|
|
6
6
|
cond = [(cond << "start_at BETWEEN ? AND ?").join(' AND ')] | [when_to.send("beginning_of_#{scale}"), when_to.send("end_of_#{scale}")] unless scale.nil?
|
7
7
|
cond
|
8
8
|
end
|
9
|
-
|
10
|
-
def spent what
|
11
|
-
Proc.new {|*scale_when|
|
12
|
-
scale, when_to = scale_when
|
13
|
-
what.send(:all, :conditions => conditions_for_time_spent(scale, when_to) ).inject(0.0) {|total, whence|
|
14
|
-
total += ( whence.end_at - whence.start_at )
|
15
|
-
} / 3600
|
16
|
-
}
|
17
|
-
end
|
18
9
|
end
|
19
10
|
end
|
data/models/project.rb
CHANGED
@@ -26,11 +26,16 @@ class Project < ActiveRecord::Base
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def time_spent scale = nil, when_to = DateTime.now
|
29
|
-
|
29
|
+
whences_since = self.whences.find :all, :conditions => conditions_for_time_spent(scale, when_to)
|
30
|
+
whences_since.inject(0.0) {|total, whence|
|
31
|
+
total += ( whence.end_at - whence.start_at )
|
32
|
+
} / 3600
|
30
33
|
end
|
31
34
|
|
32
35
|
def amount scale = nil, when_to = Time.now
|
33
|
-
|
36
|
+
amount = time_spent(scale, when_to)
|
37
|
+
amount *= payment.rate / 100.0
|
38
|
+
amount.to_money
|
34
39
|
end
|
35
40
|
|
36
41
|
class << self
|
@@ -47,6 +52,15 @@ class Project < ActiveRecord::Base
|
|
47
52
|
all - [primary, off]
|
48
53
|
end
|
49
54
|
|
55
|
+
def longest_project_name
|
56
|
+
project_names = all.collect(&:name)
|
57
|
+
longest = project_names.inject(0) do |max, next_name|
|
58
|
+
max = next_name.length if next_name.length > max
|
59
|
+
max
|
60
|
+
end
|
61
|
+
longest
|
62
|
+
end
|
63
|
+
|
50
64
|
def migrate which = :up
|
51
65
|
ActiveRecord::Schema.define do
|
52
66
|
if which == :up
|
@@ -55,26 +69,6 @@ class Project < ActiveRecord::Base
|
|
55
69
|
t.integer :weight, :default => -1
|
56
70
|
t.references :customer, :null => false
|
57
71
|
end
|
58
|
-
|
59
|
-
Project.create(
|
60
|
-
[
|
61
|
-
{
|
62
|
-
:name => 'Refactor',
|
63
|
-
:weight => 1,
|
64
|
-
:customer_id => 0
|
65
|
-
},
|
66
|
-
{
|
67
|
-
:name => 'Lunch/Break',
|
68
|
-
:weight => 0,
|
69
|
-
:customer_id => 0
|
70
|
-
},
|
71
|
-
{
|
72
|
-
:name => 'Other',
|
73
|
-
:weight => -1,
|
74
|
-
:customer_id => 0
|
75
|
-
}
|
76
|
-
]
|
77
|
-
)
|
78
72
|
elsif which == :down
|
79
73
|
drop_table :projects
|
80
74
|
end
|
data/models/whence.rb
CHANGED
@@ -2,6 +2,8 @@ require 'models/pratt'
|
|
2
2
|
|
3
3
|
class Whence < ActiveRecord::Base
|
4
4
|
belongs_to :project
|
5
|
+
has_many :invoice_whences
|
6
|
+
has_many :invoices, :through => :invoice_whences
|
5
7
|
validates_associated :project
|
6
8
|
|
7
9
|
def stop! when_to
|
@@ -33,12 +35,19 @@ class Whence < ActiveRecord::Base
|
|
33
35
|
def inspect
|
34
36
|
"#<Whence id: '#{id || ''}', project: '#{project ? project.name : ''}', start_at: '#{start_at? ? start_at.strftime(Pratt::FMT) : ''}', end_at: '#{end_at? ? end_at.strftime(Pratt::FMT) : ''}', invoiced: #{invoiced ? 'true' : 'false'}>"
|
35
37
|
end
|
38
|
+
|
39
|
+
def invoiced?
|
40
|
+
read_attribute(:invoiced) == true
|
41
|
+
end
|
36
42
|
|
37
43
|
class << self
|
38
44
|
include Pratt::TimeSpent
|
39
45
|
|
40
46
|
def time_spent scale = nil, when_to = Time.now
|
41
|
-
|
47
|
+
whences_since = Whence.find :all, :conditions => conditions_for_time_spent(scale, when_to)
|
48
|
+
whences_since.inject(0.0) {|total, whence|
|
49
|
+
total += ( whence.end_at - whence.start_at )
|
50
|
+
} / 3600
|
42
51
|
end
|
43
52
|
|
44
53
|
def last_unended
|
data/models/zip.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
class Zip < ActiveRecord::Base
|
2
|
+
|
3
|
+
validates_presence_of :zip, :city, :ST, :state, :longitude, :latitude
|
4
|
+
|
5
|
+
def before_validation_on_create
|
6
|
+
self.ST = self.state[0,2].upcase
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def migrate which = :up
|
11
|
+
ActiveRecord::Schema.define do
|
12
|
+
if which == :up
|
13
|
+
create_table :zips do |t|
|
14
|
+
t.string :zip, :null => false
|
15
|
+
t.string :city, :null => false
|
16
|
+
t.string :ST, :null => false, :limit => 2
|
17
|
+
t.string :state, :null => false
|
18
|
+
t.float :longitude, :null => false
|
19
|
+
t.float :latitude, :null => false
|
20
|
+
end
|
21
|
+
elsif which == :down
|
22
|
+
drop_table :zips
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/spec/float_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pratt'
|
3
|
+
|
4
|
+
describe Float do
|
5
|
+
it { 2.0.should respond_to(:format_integer) }
|
6
|
+
it { 2.0.should respond_to(:percentage) }
|
7
|
+
|
8
|
+
it "should format correctly" do
|
9
|
+
2.0.format_integer.should eql("02")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "shows percentage correctly with no total" do
|
13
|
+
2.3.percentage.should eql("230.00%")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "shows percentage correctly with total" do
|
17
|
+
2.5.percentage(5).should eql("50.00%")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "handles with_label as expected" do
|
21
|
+
Project.expects(:longest_project_name).returns 21
|
22
|
+
3.5.with_label("label").should eql(" label 3.5")
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pratt'
|
3
|
+
|
4
|
+
describe Numeric do
|
5
|
+
it { 1.should respond_to(:format_integer) }
|
6
|
+
it { 1.should respond_to(:with_label) }
|
7
|
+
|
8
|
+
it "should format correctly" do
|
9
|
+
Pratt.color = false
|
10
|
+
1.format_integer.should eql("01")
|
11
|
+
end
|
12
|
+
|
13
|
+
it '"format" with with_label' do
|
14
|
+
Pratt.color = false
|
15
|
+
Project.expects(:longest_project_name).returns(11)
|
16
|
+
1.with_label(:blue).should eql(" blue 1")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "recursively applies formatting without color" do
|
20
|
+
Pratt.color = false
|
21
|
+
Project.expects(:longest_project_name).returns(11)
|
22
|
+
1.format_integer.blue.with_label("#").should eql(" # 01")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "recursively applies formatting with color" do
|
26
|
+
Pratt.color = true
|
27
|
+
Project.expects(:longest_project_name).returns(11)
|
28
|
+
1.format_integer.blue.with_label("#").should eql(" # \e[34m01\e[0m")
|
29
|
+
end
|
30
|
+
end
|
data/spec/pratt_spec.rb
CHANGED
@@ -55,8 +55,8 @@ describe Pratt do
|
|
55
55
|
|
56
56
|
describe "\b#root" do
|
57
57
|
before :each do
|
58
|
-
|
59
|
-
@expected_root
|
58
|
+
@expected_root = File.dirname( File.expand_path( '..', __FILE__ ) ).to_s
|
59
|
+
Dir.stubs(:pwd).returns(@expected_root)
|
60
60
|
end
|
61
61
|
|
62
62
|
it "is correct without arguments" do
|
@@ -75,8 +75,9 @@ describe Pratt do
|
|
75
75
|
|
76
76
|
it "is correct with an argument and block" do
|
77
77
|
received = []
|
78
|
+
expected = %w(app.rb customer.rb project.rb payment.rb pratt.rb whence.rb invoice.rb invoice_whence.rb zip.rb)
|
78
79
|
Pratt.root('models', '*.rb') {|model| received << model }
|
79
|
-
received.to_set.should ==
|
80
|
+
received.to_set.should == expected.collect {|model| Pathname.new File.join(@expected_root, "models", model) }.to_set
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
@@ -125,7 +126,7 @@ describe Pratt do
|
|
125
126
|
|
126
127
|
it "should look right with data" do
|
127
128
|
populate_with_data
|
128
|
-
|
129
|
+
@pratt.expects(:process_template!)
|
129
130
|
|
130
131
|
@pratt.graph.should == get_expected_display
|
131
132
|
end
|
data/spec/project_spec.rb
CHANGED
@@ -10,6 +10,11 @@ end
|
|
10
10
|
describe Project do
|
11
11
|
it_should_behave_like "being a billable item"
|
12
12
|
|
13
|
+
it{ Project.should respond_to(:primary) }
|
14
|
+
it{ Project.should respond_to(:off) }
|
15
|
+
it{ Project.should respond_to(:rest) }
|
16
|
+
it{ Project.should respond_to(:longest_project_name) }
|
17
|
+
|
13
18
|
context "scopes" do
|
14
19
|
it "named primary" do
|
15
20
|
Project.expects(:first).with(:conditions => { :weight => 1 }).returns(mock('Refactor'))
|
@@ -96,7 +101,7 @@ describe Project do
|
|
96
101
|
|
97
102
|
context "amount calculation" do
|
98
103
|
before :each do
|
99
|
-
Payment.create :rate => '315', :billable => @project
|
104
|
+
Payment.create :rate => '315.0', :billable => @project
|
100
105
|
|
101
106
|
@now = Time.parse("2009-10-04 17:53:58")
|
102
107
|
Time.stubs(:now).returns(@now.beginning_of_week)
|
@@ -132,7 +137,7 @@ describe Project do
|
|
132
137
|
end
|
133
138
|
|
134
139
|
it "correctly calculates with no data" do
|
135
|
-
|
140
|
+
@project.expects(:time_spent).returns 0
|
136
141
|
@project.time_spent.should == 0.0
|
137
142
|
end
|
138
143
|
|
@@ -160,4 +165,5 @@ describe Project do
|
|
160
165
|
@project.time_spent('day', Chronic.parse('yesterday')).should == 18.0/60
|
161
166
|
end
|
162
167
|
end
|
168
|
+
|
163
169
|
end
|
data/spec/spec.opts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
--color
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
2
2
|
# from the project root directory.
|
3
|
+
require 'rubygems'
|
3
4
|
require 'spec'
|
4
|
-
require '
|
5
|
+
require 'mocha'
|
6
|
+
require 'lib/pratt'
|
5
7
|
require 'ruby-debug'
|
6
8
|
|
7
9
|
Spec::Runner.configure do |config|
|
@@ -9,6 +11,34 @@ Spec::Runner.configure do |config|
|
|
9
11
|
config.mock_with :mocha
|
10
12
|
|
11
13
|
Pratt.connect! 'test'
|
14
|
+
|
15
|
+
Customer.create(
|
16
|
+
:name => 'Scott Noel-Hemming',
|
17
|
+
:company_name => 'Frogstarr78 Software',
|
18
|
+
:address => '312 NW 7th',
|
19
|
+
:phone => '509.730.5401',
|
20
|
+
:zip => '97862'
|
21
|
+
) unless Customer.count > 0
|
22
|
+
|
23
|
+
Project.create(
|
24
|
+
[
|
25
|
+
{
|
26
|
+
:name => 'Refactor',
|
27
|
+
:weight => 1,
|
28
|
+
:customer_id => 0
|
29
|
+
},
|
30
|
+
{
|
31
|
+
:name => 'Lunch/Break',
|
32
|
+
:weight => 0,
|
33
|
+
:customer_id => 0
|
34
|
+
},
|
35
|
+
{
|
36
|
+
:name => 'Other',
|
37
|
+
:weight => -1,
|
38
|
+
:customer_id => 0
|
39
|
+
}
|
40
|
+
]
|
41
|
+
) unless Project.count > 0
|
12
42
|
end
|
13
43
|
|
14
44
|
shared_examples_for "Time spent on a project" do
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pratt'
|
3
|
+
|
4
|
+
describe String do
|
5
|
+
it { subject.should respond_to(:colorize) }
|
6
|
+
it { subject.should respond_to(:or) }
|
7
|
+
it { subject.should respond_to(:with_label) }
|
8
|
+
|
9
|
+
it "doesn't process alternate format when Pratt.color? == false" do
|
10
|
+
Pratt.color = false
|
11
|
+
"abc".or("xyz").should eql("abc")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "processes alternate format when Pratt.color? == true" do
|
15
|
+
Pratt.color = true
|
16
|
+
"abc".or("xyz").should eql("xyz")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "doesn't colorize when Pratt.color? == false" do
|
20
|
+
Pratt.color = false
|
21
|
+
"abc".blue.should eql("abc")
|
22
|
+
end
|
23
|
+
|
24
|
+
it "colorizes when Pratt.color? == true" do
|
25
|
+
Pratt.color = true
|
26
|
+
"abc".blue.should eql("\e[34mabc\e[0m")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "handles with_label as expected" do
|
30
|
+
Project.expects(:longest_project_name).returns 11
|
31
|
+
"abc".with_label("label").should eql(" label abc")
|
32
|
+
end
|
33
|
+
end
|
data/views/current.eruby
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
projects: <%= @project_names.join ' ' %>
|
2
|
+
<% if @last_whence.end_at.nil? %>
|
3
|
+
started: <%= @last_whence.start_at.strftime(Pratt::FMT).send(:blue) %>
|
4
|
+
next prompt: <%= ( @time_til / 60.0 ).format_integer.yellow %> min <%= ( @time_til % 60 ).format_integer.yellow %> sec
|
5
|
+
<% end %>
|
@@ -0,0 +1,538 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" ?>
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
3
|
+
<!--This file was converted to xhtml by OpenOffice.org - see http://xml.openoffice.org/odf2xhtml for more info.-->
|
4
|
+
<head>
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
6
|
+
<title xml:lang="en-US">Frogstarr78 Software Invoice</title>
|
7
|
+
|
8
|
+
<style type="text/css">
|
9
|
+
@page { }
|
10
|
+
body {
|
11
|
+
padding: 15px;
|
12
|
+
}
|
13
|
+
table {
|
14
|
+
border-collapse: collapse;
|
15
|
+
border-spacing: 0;
|
16
|
+
empty-cells: show
|
17
|
+
}
|
18
|
+
td, th {
|
19
|
+
vertical-align: top;
|
20
|
+
font-size: 10pt;
|
21
|
+
}
|
22
|
+
* {
|
23
|
+
margin: 0;
|
24
|
+
}
|
25
|
+
.Default {
|
26
|
+
font-family: Arial;
|
27
|
+
}
|
28
|
+
.ce1 {
|
29
|
+
color: #4CBB17;
|
30
|
+
font-family: sans-serif;
|
31
|
+
font-size: 16pt;
|
32
|
+
font-variant: small-caps;
|
33
|
+
}
|
34
|
+
.ce11 {
|
35
|
+
font-family: Arial;
|
36
|
+
border: 1px solid #666666;
|
37
|
+
border-top: none;
|
38
|
+
vertical-align: top;
|
39
|
+
text-align: left ! important;
|
40
|
+
margin-left: 0in;
|
41
|
+
}
|
42
|
+
.ce12 {
|
43
|
+
font-family: Arial;
|
44
|
+
border-style: none;
|
45
|
+
vertical-align: top;
|
46
|
+
text-align: center ! important;
|
47
|
+
margin-left: 0in;
|
48
|
+
font-size: 10pt;
|
49
|
+
font-style: normal;
|
50
|
+
text-shadow: none;
|
51
|
+
text-decoration: none ! important;
|
52
|
+
font-weight: normal;
|
53
|
+
}
|
54
|
+
.ce13 {
|
55
|
+
font-family: Arial;
|
56
|
+
border-style: none;
|
57
|
+
vertical-align: top;
|
58
|
+
text-align: center ! important;
|
59
|
+
margin-left: 0in;
|
60
|
+
font-size: 10pt;
|
61
|
+
font-style: normal;
|
62
|
+
text-shadow: none;
|
63
|
+
text-decoration: none ! important;
|
64
|
+
font-weight: normal;
|
65
|
+
}
|
66
|
+
.ce14 {
|
67
|
+
font-family: Trebuchet MS;
|
68
|
+
border-style: none;
|
69
|
+
vertical-align: top;
|
70
|
+
text-align: center ! important;
|
71
|
+
margin-left: 0in;
|
72
|
+
font-size: 12pt;
|
73
|
+
font-style: italic;
|
74
|
+
text-shadow: none;
|
75
|
+
text-decoration: none ! important;
|
76
|
+
font-weight: bold;
|
77
|
+
}
|
78
|
+
.ce15 {
|
79
|
+
font-family: Trebuchet MS;
|
80
|
+
border-style: none;
|
81
|
+
vertical-align: top;
|
82
|
+
text-align: right ! important;
|
83
|
+
margin-left: 0in;
|
84
|
+
color: #4CBB17;
|
85
|
+
font-size: 28pt;
|
86
|
+
font-style: normal;
|
87
|
+
text-shadow: none;
|
88
|
+
text-decoration: none ! important;
|
89
|
+
font-weight: bold;
|
90
|
+
}
|
91
|
+
.ce16 {
|
92
|
+
font-family: Arial;
|
93
|
+
border-style: none;
|
94
|
+
vertical-align: top;
|
95
|
+
text-align: center ! important;
|
96
|
+
margin-left: 0in;
|
97
|
+
}
|
98
|
+
.ce18 {
|
99
|
+
font-family: Arial;
|
100
|
+
border: none;
|
101
|
+
border-bottom: 1px solid #000000;
|
102
|
+
vertical-align: top;
|
103
|
+
text-align: center ! important;
|
104
|
+
margin-left: 0in;
|
105
|
+
}
|
106
|
+
.ce20 {
|
107
|
+
font-family: Trebuchet MS;
|
108
|
+
background-color: transparent;
|
109
|
+
vertical-align: top;
|
110
|
+
text-align: left ! important;
|
111
|
+
margin-left: 0in;
|
112
|
+
font-size: 10pt;
|
113
|
+
font-style: normal;
|
114
|
+
text-shadow: none;
|
115
|
+
text-decoration: none ! important;
|
116
|
+
font-weight: bold;
|
117
|
+
}
|
118
|
+
.ce21 {
|
119
|
+
font-family: Trebuchet MS;
|
120
|
+
font-size: 10pt;
|
121
|
+
font-style: normal;
|
122
|
+
text-shadow: none;
|
123
|
+
text-decoration: none ! important;
|
124
|
+
font-weight: bold;
|
125
|
+
}
|
126
|
+
.ce22 {
|
127
|
+
font-family: Arial;
|
128
|
+
border: none;
|
129
|
+
border-right: 1px solid #000000;
|
130
|
+
vertical-align: top;
|
131
|
+
text-align: center ! important;
|
132
|
+
margin-left: 0in;
|
133
|
+
}
|
134
|
+
.ce25 {
|
135
|
+
font-family: Arial;
|
136
|
+
border: none;
|
137
|
+
vertical-align: top;
|
138
|
+
text-align: center ! important;
|
139
|
+
margin-left: 0in;
|
140
|
+
font-size: 10pt;
|
141
|
+
font-style: normal;
|
142
|
+
text-shadow: none;
|
143
|
+
text-decoration: none ! important;
|
144
|
+
font-weight: normal;
|
145
|
+
}
|
146
|
+
.ce26 {
|
147
|
+
font-family: Trebuchet MS;
|
148
|
+
border-style: none;
|
149
|
+
vertical-align: top;
|
150
|
+
text-align: center ! important;
|
151
|
+
margin-left: 0in;
|
152
|
+
font-size: 10pt;
|
153
|
+
font-style: normal;
|
154
|
+
text-shadow: none;
|
155
|
+
text-decoration: none ! important;
|
156
|
+
font-weight: bold;
|
157
|
+
}
|
158
|
+
.ce28 {
|
159
|
+
font-size: 10pt;
|
160
|
+
font-style: normal;
|
161
|
+
text-shadow: none;
|
162
|
+
text-decoration: none ! important;
|
163
|
+
font-weight: normal;
|
164
|
+
}
|
165
|
+
.ce29 {
|
166
|
+
font-family: Arial;
|
167
|
+
border: 1px solid #000000;
|
168
|
+
vertical-align: top;
|
169
|
+
text-align: center ! important;
|
170
|
+
margin-left: 0in;
|
171
|
+
}
|
172
|
+
.ce3 {
|
173
|
+
font-family: Arial;
|
174
|
+
font-size: 10pt;
|
175
|
+
font-style: normal;
|
176
|
+
text-shadow: none;
|
177
|
+
text-decoration: none ! important;
|
178
|
+
font-weight: normal;
|
179
|
+
}
|
180
|
+
.ce31 {
|
181
|
+
font-family: Arial;
|
182
|
+
border-bottom: none;
|
183
|
+
border-left: 0.0879cm solid #000000;
|
184
|
+
border-right: 0.0879cm solid #000000;
|
185
|
+
border-top: none;
|
186
|
+
}
|
187
|
+
.ce32 {
|
188
|
+
font-family: Arial;
|
189
|
+
border: 0.0879cm solid #000000;
|
190
|
+
border-top: none;
|
191
|
+
}
|
192
|
+
.ce34 {
|
193
|
+
font-family: Arial;
|
194
|
+
border: none;
|
195
|
+
}
|
196
|
+
.ce35 {
|
197
|
+
font-family: Arial;
|
198
|
+
border: none;
|
199
|
+
border-bottom: 0.1049cm double #000000;
|
200
|
+
background-color: transparent;
|
201
|
+
}
|
202
|
+
.ce36 {
|
203
|
+
font-family: Trebuchet MS;
|
204
|
+
border-bottom: 0.1049cm double #000000;
|
205
|
+
background-color: transparent;
|
206
|
+
font-size: 10pt;
|
207
|
+
font-style: normal;
|
208
|
+
text-shadow: none;
|
209
|
+
text-decoration: none
|
210
|
+
}
|
211
|
+
.ce5 {
|
212
|
+
background-color: #4CBB17;
|
213
|
+
border: none;
|
214
|
+
color: #ffffff;
|
215
|
+
font-family: Trebuchet MS;
|
216
|
+
font-size: 10pt;
|
217
|
+
font-style: normal;
|
218
|
+
font-weight: bold;
|
219
|
+
margin-left: 0in;
|
220
|
+
text-align: center ! important;
|
221
|
+
text-decoration: none ! important;
|
222
|
+
text-shadow: none;
|
223
|
+
vertical-align: top;
|
224
|
+
}
|
225
|
+
.ce30 {
|
226
|
+
border: 0.0879cm solid #000000;
|
227
|
+
border-bottom: none;
|
228
|
+
}
|
229
|
+
.ceN {
|
230
|
+
vertical-align: top;
|
231
|
+
text-align: left ! important;
|
232
|
+
margin-left: 0in;
|
233
|
+
font-size: 10pt;
|
234
|
+
font-style: normal;
|
235
|
+
text-shadow: none;
|
236
|
+
text-decoration: none ! important;
|
237
|
+
font-weight: normal;
|
238
|
+
}
|
239
|
+
.ce6 {
|
240
|
+
padding-left: 7px;
|
241
|
+
font-family: Arial;
|
242
|
+
border-bottom: none;
|
243
|
+
border-left: 1px solid #000000;
|
244
|
+
border-right: 1px solid #000000;
|
245
|
+
border-top: none;
|
246
|
+
|
247
|
+
}
|
248
|
+
.ce8 {
|
249
|
+
font-family: Trebuchet MS;
|
250
|
+
border: none;
|
251
|
+
border-bottom: 1px solid #666666;
|
252
|
+
background-color: #c0c0c0;
|
253
|
+
font-weight: bold;
|
254
|
+
}
|
255
|
+
.ce10 {
|
256
|
+
font-family: Arial;
|
257
|
+
border: 1px solid #666666;
|
258
|
+
border-bottom: none;
|
259
|
+
border-top: none;
|
260
|
+
}
|
261
|
+
.ro1 {
|
262
|
+
height: 0.4736in;
|
263
|
+
}
|
264
|
+
.ro2 {
|
265
|
+
height: 0.1736in;
|
266
|
+
}
|
267
|
+
.ro3 {
|
268
|
+
height: 0.1807in;
|
269
|
+
}
|
270
|
+
.ro4 {
|
271
|
+
height: 0.2071in;
|
272
|
+
}
|
273
|
+
|
274
|
+
.TN {
|
275
|
+
font-style: normal;
|
276
|
+
text-shadow: none;
|
277
|
+
font-weight: normal;
|
278
|
+
text-decoration: none;
|
279
|
+
font-family: Trebuchet MS;
|
280
|
+
}
|
281
|
+
|
282
|
+
.T1 {
|
283
|
+
float: left;
|
284
|
+
font-size: 38pt;
|
285
|
+
text-decoration: underline;
|
286
|
+
}
|
287
|
+
.T2 {
|
288
|
+
font-size: 16pt;
|
289
|
+
}
|
290
|
+
.T3 {
|
291
|
+
font-family: Arial;
|
292
|
+
font-size: 10pt;
|
293
|
+
}
|
294
|
+
</style>
|
295
|
+
|
296
|
+
</head>
|
297
|
+
|
298
|
+
<body dir="ltr">
|
299
|
+
<table border="0" cellspacing="0" cellpadding="0" class="ta1">
|
300
|
+
<colgroup>
|
301
|
+
<col width="224"/>
|
302
|
+
<col width="181"/>
|
303
|
+
<col width="100"/>
|
304
|
+
<col width="100"/>
|
305
|
+
<col width="140"/>
|
306
|
+
</colgroup>
|
307
|
+
|
308
|
+
<tr class="ro1">
|
309
|
+
<td colspan="3" class="ce1">
|
310
|
+
<p>
|
311
|
+
<span style="float:left;">
|
312
|
+
<span style="float: left; height: 25px; padding-top: 11px;">
|
313
|
+
Frogstarr
|
314
|
+
</span>
|
315
|
+
|
316
|
+
<span style="float: left; clear: left;">
|
317
|
+
Software
|
318
|
+
</span>
|
319
|
+
</span>
|
320
|
+
<span class="TN T1">78</span>
|
321
|
+
</p>
|
322
|
+
</td>
|
323
|
+
<td colspan="2" class="ce15">
|
324
|
+
<p>INVOICE</p>
|
325
|
+
</td>
|
326
|
+
</tr>
|
327
|
+
|
328
|
+
<tr class="ro2">
|
329
|
+
<td colspan="2" class="ce3">
|
330
|
+
<p>312 NW 7<span class="TN T3">th</span></p>
|
331
|
+
</td>
|
332
|
+
<td class="Default"> </td>
|
333
|
+
<td class="ce20">
|
334
|
+
<p>DATE:</p>
|
335
|
+
</td>
|
336
|
+
<td style="text-align: right; background-color: #AAE09A;" class="ce29">
|
337
|
+
<p><%= when_to.send("beginning_of_#{scale}").strftime(Pratt::INVOICE_FMT) %> to <%= when_to.send("end_of_#{scale}").strftime(Pratt::INVOICE_FMT) %></p>
|
338
|
+
</td>
|
339
|
+
</tr>
|
340
|
+
<tr class="ro2">
|
341
|
+
<td colspan="2" class="ce3">
|
342
|
+
<p>Milton-Freewater, OR 97862</p>
|
343
|
+
</td>
|
344
|
+
<td class="Default"> </td>
|
345
|
+
<td class="ce20">
|
346
|
+
<p>INVOICE #</p>
|
347
|
+
</td>
|
348
|
+
<td class="ce29 ce28">
|
349
|
+
<p><%= project.customer.id %>-<%= when_to.week %>-01</p>
|
350
|
+
</td>
|
351
|
+
</tr>
|
352
|
+
<tr class="ro3">
|
353
|
+
<td colspan="2" class="ce3">
|
354
|
+
<p>Phone: 509-730-5401</p>
|
355
|
+
</td>
|
356
|
+
<td class="Default"> </td>
|
357
|
+
<td class="ce21">
|
358
|
+
<p>Customer ID</p>
|
359
|
+
</td>
|
360
|
+
<td style="text-align: right;" class="ce29">
|
361
|
+
<p><%= project.customer.id %></p>
|
362
|
+
</td>
|
363
|
+
</tr>
|
364
|
+
|
365
|
+
<tr class="ro2">
|
366
|
+
<td colspan="5" class="Default"> </td>
|
367
|
+
</tr>
|
368
|
+
<tr class="ro2">
|
369
|
+
<td colspan="5" class="Default"> </td>
|
370
|
+
</tr>
|
371
|
+
|
372
|
+
<tr class="ro2">
|
373
|
+
<td style="border-bottom: 1px solid #000000; text-align: left;" class="ce5">
|
374
|
+
<p>BILL TO:</p>
|
375
|
+
</td>
|
376
|
+
</tr>
|
377
|
+
<tr class="ro2">
|
378
|
+
<td class="ce3">
|
379
|
+
<p><%= project.customer.name %></p>
|
380
|
+
</td>
|
381
|
+
</tr>
|
382
|
+
<%
|
383
|
+
if project.customer.address
|
384
|
+
%>
|
385
|
+
<tr class="ro2">
|
386
|
+
<td class="ce3">
|
387
|
+
<p><%= project.customer.address %></p>
|
388
|
+
</td>
|
389
|
+
</tr>
|
390
|
+
<%
|
391
|
+
end
|
392
|
+
|
393
|
+
if project.customer.company_name
|
394
|
+
%>
|
395
|
+
<tr class="ro2">
|
396
|
+
<td class="ce3">
|
397
|
+
<p><%= project.customer.company_name %></p>
|
398
|
+
</td>
|
399
|
+
</tr>
|
400
|
+
<%
|
401
|
+
end
|
402
|
+
|
403
|
+
if project.customer.zip
|
404
|
+
%>
|
405
|
+
<tr class="ro2">
|
406
|
+
<td class="ce3">
|
407
|
+
<p><%= project.customer.city_state_zip %></p>
|
408
|
+
</td>
|
409
|
+
</tr>
|
410
|
+
<%
|
411
|
+
end
|
412
|
+
|
413
|
+
if project.customer.phone
|
414
|
+
%>
|
415
|
+
<tr class="ro2">
|
416
|
+
<td class="ce3">
|
417
|
+
<p><%= project.customer.phone.pretty_print %></p>
|
418
|
+
</td>
|
419
|
+
</tr>
|
420
|
+
<%
|
421
|
+
end
|
422
|
+
%>
|
423
|
+
<tr class="ro2">
|
424
|
+
<td class="Default"> </td>
|
425
|
+
</tr>
|
426
|
+
<tr class="ro2">
|
427
|
+
<td colspan="2" class="ce5">
|
428
|
+
<p>DESCRIPTION</p>
|
429
|
+
</td>
|
430
|
+
<td class="ce5">
|
431
|
+
<p>HOURS</p>
|
432
|
+
</td>
|
433
|
+
<td class="ce5">
|
434
|
+
<p>RATE</p>
|
435
|
+
</td>
|
436
|
+
<td class="ce5 ce30">
|
437
|
+
<p>AMOUNT</p>
|
438
|
+
</td>
|
439
|
+
</tr>
|
440
|
+
|
441
|
+
<% @projects.each do |project| %>
|
442
|
+
<tr class="ro2">
|
443
|
+
<td colspan="2" class="ceN ce6">
|
444
|
+
<p><%= project.name %></p>
|
445
|
+
</td>
|
446
|
+
<td style="text-align:right; " class="ce16">
|
447
|
+
<p><%= project.time_spent(scale, when_to).pretty_print %></p>
|
448
|
+
</td>
|
449
|
+
<td style="text-align:right; " class="ce22">
|
450
|
+
<p><%= project.payment.pretty_print %></p>
|
451
|
+
</td>
|
452
|
+
<td style="text-align:right; " class="ce31">
|
453
|
+
<p><%= project.amount(scale, when_to).pretty_print %></p>
|
454
|
+
</td>
|
455
|
+
</tr>
|
456
|
+
<% end %>
|
457
|
+
<tr class="ro2">
|
458
|
+
<td colspan="2" style="border: 1px solid black; border-top: none;"> </td>
|
459
|
+
<td class="ce18"> </td>
|
460
|
+
<td style="border-right: 1px solid #000000;" class="ce18"> </td>
|
461
|
+
<td class="ce32"> </td>
|
462
|
+
</tr>
|
463
|
+
|
464
|
+
<tr class="ro2">
|
465
|
+
<td colspan="2" class="Default"> </td>
|
466
|
+
<td class="Default"> </td>
|
467
|
+
<td class="Default ce35">
|
468
|
+
<!--p>SUBTOTAL</p-->
|
469
|
+
</td>
|
470
|
+
<td style="text-align:right; " class="ce36">
|
471
|
+
<!--p><%= project.amount(scale, when_to).pretty_print %></p-->
|
472
|
+
</td>
|
473
|
+
</tr>
|
474
|
+
<tr class="ro2">
|
475
|
+
<td colspan="2" class="ceN ce8">
|
476
|
+
<p>OTHER COMMENTS</p>
|
477
|
+
</td>
|
478
|
+
<td class="Default"> </td>
|
479
|
+
<td class="ce21">
|
480
|
+
<p>TOTAL</p>
|
481
|
+
</td>
|
482
|
+
<td style="text-align:right; " class="ce21 ce34">
|
483
|
+
<p><%= project.amount(scale, when_to).pretty_print %></p>
|
484
|
+
</td>
|
485
|
+
</tr>
|
486
|
+
<tr class="ro2">
|
487
|
+
<td colspan="2" class="ceN ce10">
|
488
|
+
<p>1. Total payment due in 30 days</p>
|
489
|
+
</td>
|
490
|
+
</tr>
|
491
|
+
<tr class="ro2">
|
492
|
+
<td colspan="2" class="ceN ce10">
|
493
|
+
<p>2. Please include the invoice number on your check</p>
|
494
|
+
</td>
|
495
|
+
<td class="Default"> </td>
|
496
|
+
<td colspan="2" class="ce25">
|
497
|
+
<p>Make all checks payable to</p>
|
498
|
+
</td>
|
499
|
+
</tr>
|
500
|
+
|
501
|
+
<tr class="ro2">
|
502
|
+
<td colspan="2" class="ceN ce10"> </td>
|
503
|
+
<td class="Default"> </td>
|
504
|
+
<td colspan="2" class="ce26">
|
505
|
+
<p>Frogstarr78 Software</p>
|
506
|
+
</td>
|
507
|
+
</tr>
|
508
|
+
|
509
|
+
<tr class="ro2">
|
510
|
+
<td colspan="2" class="ce11"> </td>
|
511
|
+
</tr>
|
512
|
+
<tr class="ro2">
|
513
|
+
<td colspan="5" class="Default"> </td>
|
514
|
+
</tr>
|
515
|
+
|
516
|
+
<tr class="ro2">
|
517
|
+
<td colspan="5" class="ce12">
|
518
|
+
<p>If you have any questions about this invoice, please contact</p>
|
519
|
+
</td>
|
520
|
+
</tr>
|
521
|
+
<tr class="ro3">
|
522
|
+
<td colspan="5" class="ce13">
|
523
|
+
<p>
|
524
|
+
Scott Noel-Hemming, 509.301.6893, <a href="mailto:frogstarr78@gmail.com">frogstarr78@gmail.com</a>
|
525
|
+
</p>
|
526
|
+
</td>
|
527
|
+
</tr>
|
528
|
+
<tr class="ro2">
|
529
|
+
<td colspan="5" class="Default"> </td>
|
530
|
+
</tr>
|
531
|
+
<tr class="ro4">
|
532
|
+
<td colspan="5" class="ce14">
|
533
|
+
<p>Thank You For Your Business!</p>
|
534
|
+
</td>
|
535
|
+
</tr>
|
536
|
+
</table>
|
537
|
+
</body>
|
538
|
+
</html>
|