rbinvoice 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.html ADDED
@@ -0,0 +1,43 @@
1
+ <h1>rbinvoice</h1>
2
+
3
+ <p>rbinvoice lets you generate PDF invoices from a Google Spreadsheet.
4
+ It's pretty obscure; you probably haven't heard of it.</p>
5
+
6
+ <h2>Input</h2>
7
+
8
+ <p>RbInvoice reads your hours from a Google Spreadsheet, which should be formatted like this:</p>
9
+
10
+ <table>
11
+ <tr>
12
+ <td colspan="7"><b style="font-size:120%">My Time Tracking</b></td>
13
+ </tr>
14
+ <tr>
15
+ <td><b>Weekday</b></td>
16
+ <td><b>Day</b></td>
17
+ <td><b>Task</b></td>
18
+ <td><b>Notes</b></td>
19
+ <td><b>Start</b></td>
20
+ <td><b>Stop</b></td>
21
+ <td><b>Total</b></td>
22
+ </tr>
23
+ <tr>
24
+ <td>T</td>
25
+ <td>3/20/2012</td>
26
+ <td>BigCorp</td>
27
+ <td>API</td>
28
+ <td>8:00</td>
29
+ <td>12:15</td>
30
+ <td>4:15</td>
31
+ </tr>
32
+ <tr>
33
+ <td>T</td>
34
+ <td>3/20/2012</td>
35
+ <td>SmallCorp</td>
36
+ <td>Shopping Cart</td>
37
+ <td>13:00</td>
38
+ <td>17:15</td>
39
+ <td>4:00</td>
40
+ </tr>
41
+ </table>
42
+
43
+ <p>Columns B, E, F, and G should have a Date format. </p>
data/README.md CHANGED
@@ -1,6 +1,57 @@
1
1
  rbinvoice
2
2
  =========
3
3
 
4
- rbinvoice lets you generate PDF invoices from a Google Spreadsheet.
4
+ RbInvoice lets you generate PDF invoices from a Google Spreadsheet.
5
5
  It's pretty obscure; you probably haven't heard of it.
6
6
 
7
+
8
+
9
+ Disclaimer
10
+ ----------
11
+
12
+ RbInvoice is not production-ready code! I keep it on Github more for my own convenience than anything else. I do use it myself to bill clients, but lots of things are hard-coded, like my company address. If you use it, you do so at your own risk! I don't guarantee anything, and I don't promise any support. If you tell your clients to write checks to Paul Jungwirth and send them to my address, that's too bad for you. :-)
13
+
14
+ Perhaps someday I'll get this code into a shareable state; it's inching there a little bit each month. But right now you should find a real invoicing solution somewhere else. All documentation here is purely in expectation of an eventual release. Things may be broken and may change, so please don't take it as a promise of anything.
15
+
16
+
17
+
18
+ Input
19
+ -----
20
+
21
+ RbInvoice reads your hours from a Google Spreadsheet, which should be formatted like this:
22
+
23
+ <table>
24
+ <tr>
25
+ <td colspan="7"><b style="font-size:120%">My Time Tracking</b></td>
26
+ </tr>
27
+ <tr>
28
+ <td><b>Weekday</b></td>
29
+ <td><b>Day</b></td>
30
+ <td><b>Task</b></td>
31
+ <td><b>Notes</b></td>
32
+ <td><b>Start</b></td>
33
+ <td><b>Stop</b></td>
34
+ <td><b>Total</b></td>
35
+ </tr>
36
+ <tr>
37
+ <td>T</td>
38
+ <td>3/20/2012</td>
39
+ <td>BigCorp</td>
40
+ <td>API</td>
41
+ <td>8:00</td>
42
+ <td>12:15</td>
43
+ <td>4:15</td>
44
+ </tr>
45
+ <tr>
46
+ <td>T</td>
47
+ <td>3/20/2012</td>
48
+ <td>SmallCorp</td>
49
+ <td>Shopping Cart</td>
50
+ <td>13:00</td>
51
+ <td>17:15</td>
52
+ <td>4:00</td>
53
+ </tr>
54
+ </table>
55
+
56
+ Columns B, E, F, and G should have a Date format. I calculate G automatically by saying `=max(0, F3 - E3)`, but if you do that, make sure you enter times in 24-hour format, because if you work through lunch (e.g. 11:00 to 1:30) your total column will be 0:00.
57
+
data/Rakefile CHANGED
@@ -58,3 +58,6 @@ task :run, [] => [] do |t, args|
58
58
  RbInvoice::write_invoice(*RbInvoice::Options::parse_command_line(%w{okvenue}))
59
59
  end
60
60
 
61
+ task :readme => [] do |task|
62
+ `markdown README.md >README.html`
63
+ end
data/TODO CHANGED
@@ -8,5 +8,6 @@
8
8
  - more tests
9
9
  - print the pdflatex results to std{out,err} only if the user gives a --verbose flag.
10
10
  - Fix LaTeX escaping for double quotes.
11
+ - Add an option to redo the last invoice for the given client.
11
12
 
12
13
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.3
1
+ 0.2.4
data/lib/rbinvoice.rb CHANGED
@@ -15,6 +15,8 @@ module RbInvoice
15
15
  COL_START_TIME = 'E'
16
16
  COL_END_TIME = 'F'
17
17
  COL_TOTAL_TIME = 'G'
18
+ COL_MONDAY = 'H'
19
+ COL_NOTES = 'I'
18
20
 
19
21
  # TODO:
20
22
  # - Figure out the next invoice_number.
@@ -43,7 +45,7 @@ module RbInvoice
43
45
  earliest_date = if last_invoice
44
46
  last_invoice[:end_date] + 1
45
47
  else
46
- parse_date(earliest_task_date)
48
+ parse_date(earliest_task_date(hours))
47
49
  end
48
50
  start_date, end_date = RbInvoice::Options::find_invoice_bounds(earliest_date, freq)
49
51
  tasks = hourly_breakdown(client, start_date, end_date, opts)
@@ -59,20 +61,25 @@ module RbInvoice
59
61
 
60
62
  def self.make_pdf(tasks, start_date, end_date, filename, opts)
61
63
  write_latex(tasks, end_date, filename, opts)
62
- system("cd \"#{File.dirname(filename)}\" && pdflatex \"#{File.basename(filename, '.pdf')}\"")
64
+ result = system("cd \"#{File.dirname(filename)}\" && pdflatex \"#{File.basename(filename, '.pdf')}\"")
65
+ raise "Problem running LaTeX: $?" unless result
63
66
  RbInvoice::Options::add_invoice_to_data(tasks, start_date, end_date, filename, opts) unless opts[:no_data_file]
64
67
  end
65
68
 
66
69
  def self.escape_for_latex(str)
67
- str.gsub('&', '\\\\&'). # tricky b/c '\&' has special meaning to gsub.
70
+ (str || '').gsub('&', '\\\\&'). # tricky b/c '\&' has special meaning to gsub.
68
71
  gsub('"', '\texttt{"}').
69
72
  gsub('$', '\$').
70
- gsub('+', '$+$')
73
+ gsub('+', '$+$').
74
+ gsub("\n", " \\\\\\\\ \n")
71
75
  end
72
76
 
73
77
  def self.write_latex(tasks, invoice_date, filename, opts)
74
78
  template = File.open(opts[:template]) { |f| f.read }
75
79
  rate = opts[:rate] # TODO: Support per-task rates
80
+ full_name = RbInvoice::Options::full_name_for_client(opts[:data], opts, opts[:client])
81
+ address = RbInvoice::Options::address_for_client(opts[:data], opts, opts[:client])
82
+ description = RbInvoice::Options::description_for_client(opts[:data], opts, opts[:client])
76
83
  items = tasks.map{|task, details|
77
84
  task_total_hours = details.inject(0) {|t, row| t + row[2]}
78
85
  {
@@ -92,6 +99,10 @@ module RbInvoice
92
99
  line_items: items,
93
100
  total_duration: decimal_to_interval(items.inject(0) {|t, item| t + item['duration_decimal']}),
94
101
  total_price: "%0.02f" % items.inject(0) {|t, item| t + item['price_decimal']},
102
+ dba: escape_for_latex(opts[:dba]),
103
+ client_full_name: escape_for_latex(full_name),
104
+ client_address: escape_for_latex(address),
105
+ client_description: escape_for_latex(description),
95
106
  }.map{|k, v| [k.to_s, v]}
96
107
  ]
97
108
  latex = Liquid::Template.parse(template).render args
@@ -127,8 +138,12 @@ module RbInvoice
127
138
  to_client_key(ss.cell(row, COL_CLIENT) || '') == client
128
139
  }.map { |row|
129
140
  raise "Invalid task times: #{ss.cell(row, COL_START_TIME)}-#{ss.cell(row, COL_END_TIME)}" if ss.cell(row, COL_START_TIME) && ss.cell(row, COL_END_TIME) && ss.cell(row, COL_TOTAL_TIME) == '0:00:00'
130
- [ss.cell(row, COL_DATE), ss.cell(row, COL_TASK), interval_to_decimal(ss.cell(row, COL_TOTAL_TIME))]
131
- }
141
+ if ss.cell(row, COL_NOTES) == 'FREE'
142
+ nil
143
+ else
144
+ [ss.cell(row, COL_DATE), ss.cell(row, COL_TASK), interval_to_decimal(ss.cell(row, COL_TOTAL_TIME))]
145
+ end
146
+ }.compact
132
147
  end
133
148
 
134
149
  def self.interval_to_decimal(time)
@@ -195,6 +195,22 @@ module RbInvoice
195
195
  key_for_client(data, client, :frequency)
196
196
  end
197
197
 
198
+ def self.dba_for_client(data, opts, client)
199
+ key_for_client(data, client, :dba) || opts[:dba] || 'Illuminated Computing Inc.'
200
+ end
201
+
202
+ def self.full_name_for_client(data, opts, client)
203
+ key_for_client(data, client, :full_name)
204
+ end
205
+
206
+ def self.address_for_client(data, opts, client)
207
+ key_for_client(data, client, :address)
208
+ end
209
+
210
+ def self.description_for_client(data, opts, client)
211
+ key_for_client(data, client, :description)
212
+ end
213
+
198
214
  def self.parse_command_line(argv)
199
215
  opts = Trollop::options(argv) do
200
216
  version "rbinvoice 0.1.0 (c) 2012 Paul A. Jungwirth"
@@ -243,6 +259,7 @@ module RbInvoice
243
259
  opts[:start_date] = Date.strptime(opts[:start_date], "%Y-%m-%d") if opts[:start_date]
244
260
  opts[:end_date] = Date.strptime(opts[:end_date], "%Y-%m-%d") if opts[:end_date]
245
261
 
262
+ opts[:dba] = dba_for_client(opts[:data], opts, opts[:client])
246
263
  # Read the list of past invoices.
247
264
  # If there are none, assume there is only one invoice to do.
248
265
 
data/rbinvoice.gemspec CHANGED
@@ -5,16 +5,17 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "rbinvoice"
8
- s.version = "0.2.3"
8
+ s.version = "0.2.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Paul A. Jungwirth"]
12
- s.date = "2012-08-23"
12
+ s.date = "2013-03-15"
13
13
  s.description = " Reads hours from a Google Spreadsheet and generates a PDF invoice.\n"
14
14
  s.email = "pj@illuminatedcomputing.com"
15
15
  s.executables = ["rbinvoice", "rbinvoice"]
16
16
  s.extra_rdoc_files = [
17
17
  "LICENSE.txt",
18
+ "README.html",
18
19
  "README.md",
19
20
  "TODO"
20
21
  ]
data/spec/options_spec.rb CHANGED
@@ -56,20 +56,20 @@ describe RbInvoice::Options do
56
56
 
57
57
  it "should compute the semimonth start date" do
58
58
  RbInvoice::Options::semimonth_start(Date.new(2011, 3, 5)).should == Date.new(2011, 3, 1)
59
- RbInvoice::Options::semimonth_start(Date.new(2011, 3,21)).should == Date.new(2011, 3,15)
60
- RbInvoice::Options::semimonth_start(Date.new(2011, 3,31)).should == Date.new(2011, 3,15)
59
+ RbInvoice::Options::semimonth_start(Date.new(2011, 3,21)).should == Date.new(2011, 3,16)
60
+ RbInvoice::Options::semimonth_start(Date.new(2011, 3,31)).should == Date.new(2011, 3,16)
61
61
  RbInvoice::Options::semimonth_start(Date.new(2011, 3, 1)).should == Date.new(2011, 3, 1)
62
62
  RbInvoice::Options::semimonth_start(Date.new(2011, 4, 8)).should == Date.new(2011, 4, 1)
63
- RbInvoice::Options::semimonth_start(Date.new(2011, 2,28)).should == Date.new(2011, 2,15)
64
- RbInvoice::Options::semimonth_start(Date.new(2012, 2,28)).should == Date.new(2012, 2,15)
65
- RbInvoice::Options::semimonth_start(Date.new(2012, 2,29)).should == Date.new(2012, 2,15)
66
- RbInvoice::Options::semimonth_start(Date.new(2011, 2,19)).should == Date.new(2011, 2,15)
67
- RbInvoice::Options::semimonth_start(Date.new(2012, 2,19)).should == Date.new(2012, 2,15)
63
+ RbInvoice::Options::semimonth_start(Date.new(2011, 2,28)).should == Date.new(2011, 2,16)
64
+ RbInvoice::Options::semimonth_start(Date.new(2012, 2,28)).should == Date.new(2012, 2,16)
65
+ RbInvoice::Options::semimonth_start(Date.new(2012, 2,29)).should == Date.new(2012, 2,16)
66
+ RbInvoice::Options::semimonth_start(Date.new(2011, 2,19)).should == Date.new(2011, 2,16)
67
+ RbInvoice::Options::semimonth_start(Date.new(2012, 2,19)).should == Date.new(2012, 2,16)
68
68
  RbInvoice::Options::semimonth_start(Date.new(2012, 1, 5)).should == Date.new(2012, 1, 1)
69
69
  RbInvoice::Options::semimonth_start(Date.new(2011,12, 1)).should == Date.new(2011,12, 1)
70
70
  RbInvoice::Options::semimonth_start(Date.new(2011,12, 5)).should == Date.new(2011,12, 1)
71
71
  RbInvoice::Options::semimonth_start(Date.new(2011,12,15)).should == Date.new(2011,12, 1)
72
- RbInvoice::Options::semimonth_start(Date.new(2011,12,25)).should == Date.new(2011,12,15)
72
+ RbInvoice::Options::semimonth_start(Date.new(2011,12,25)).should == Date.new(2011,12,16)
73
73
  end
74
74
 
75
75
  it "should compute the previous semimonth" do
@@ -8,7 +8,7 @@
8
8
 
9
9
  \noindent
10
10
  \begin{minipage}[t]{3in}
11
- Paul Jungwirth \\
11
+ {{ dba }} \\
12
12
  2520 SW Edgemoor Ave. \\
13
13
  Beaverton, OR 97005 \\
14
14
  909 557-0421 \\
@@ -25,13 +25,13 @@ Beaverton, OR 97005 \\
25
25
  {% endraw %}
26
26
 
27
27
  \noindent {\sc to}: \\
28
- OK Venue LLC \\
29
- 116 W 23rd St. \\
30
- New York, NY 10011 \\
28
+ {{ client_full_name }} \\
29
+ {% if client_address %}{{ client_address }} \\
30
+ {% endif %}
31
31
  \smallskip
32
32
 
33
33
  \noindent {\sc for}: \\
34
- Consulting and programming for the okvenue.com website and related projects. \\
34
+ {{ client_description }} \\
35
35
  \linebreak[4]
36
36
 
37
37
  \begin{tabular}{lrr}
@@ -45,8 +45,8 @@ Consulting and programming for the okvenue.com website and related projects. \\
45
45
  \bigskip
46
46
 
47
47
  \noindent
48
- Please make all checks payable to Paul Jungwirth. \\
49
- Payment due net 30 days. \\
48
+ Please make all checks payable to {{ dba }}. \\
49
+ Payment due upon receipt. \\
50
50
 
51
51
  \begin{center}
52
52
  \textbf{Thank you for your business!}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbinvoice
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-23 00:00:00.000000000 Z
12
+ date: 2013-03-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: trollop
@@ -148,6 +148,7 @@ executables:
148
148
  extensions: []
149
149
  extra_rdoc_files:
150
150
  - LICENSE.txt
151
+ - README.html
151
152
  - README.md
152
153
  - TODO
153
154
  files:
@@ -166,6 +167,7 @@ files:
166
167
  - rbinvoice.gemspec
167
168
  - spec/options_spec.rb
168
169
  - templates/invoice.tex.liquid
170
+ - README.html
169
171
  homepage: http://github.com/pjungwir/rbinvoice
170
172
  licenses:
171
173
  - MIT
@@ -181,7 +183,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
181
183
  version: '0'
182
184
  segments:
183
185
  - 0
184
- hash: 1152283729066738972
186
+ hash: 2995707456041748597
185
187
  required_rubygems_version: !ruby/object:Gem::Requirement
186
188
  none: false
187
189
  requirements: