freshtrack 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,8 @@
1
+ == 0.4.1 2009-07-31
2
+
3
+ * 1 tiny enhancement:
4
+ * Added --aging option to list open invoices and ages
5
+
1
6
  == 0.4.0 2008-08-29
2
7
 
3
8
  * 1 minor enhancement:
data/README.txt CHANGED
@@ -46,6 +46,15 @@ Freshtrack is used to automatically create time entries in FreshBooks from your
46
46
  The 'collector' is what freshtrack will use to gather the time data that will end up as FreshBooks time entries.
47
47
  Freshtrack ships with two collectors: 'punch' and 'one_inch_punch'. These are both gems that can be installed (by `gem install [collector name]`) and used without much effort. If these time-tracking tools aren't to your liking, you are free to write your own collector. Further documentation on that is forthcoming, but for now just take a look at the two collectors that already exist.
48
48
 
49
+ Collector requirements:
50
+
51
+ * It needs to be a class in the FreshTrack::TimeCollector namespace
52
+ * The file needs to exist in the freshtrack/time_collectors directory
53
+ * The class name should match the file name like Punch -> punch.rb or OneInchPunch -> one_inch_punch.rb
54
+ * It needs an initializer that takes an options argument (which will be a hash possibly having :before and :after keys with Date values)
55
+ * It needs a get_time_data instance method which takes a project name argument
56
+ * The get_time_data method needs to return an array of hashes like { 'date' => date_object, 'hours' => number_of_hours_as_float, 'notes' => notes_string }
57
+
49
58
  == INSTALL:
50
59
 
51
60
  * gem install freshtrack
data/bin/freshtrack CHANGED
@@ -13,6 +13,7 @@ require 'freshtrack'
13
13
  require 'optparse'
14
14
  require 'time'
15
15
 
16
+ aging = false
16
17
  OPTIONS = {}
17
18
  MANDATORY_OPTIONS = %w[]
18
19
 
@@ -31,6 +32,9 @@ BANNER
31
32
  "Restrict command to only before the given time") { |time| OPTIONS[:before] = Time.parse(time) }
32
33
  opts.on("-h", "--help",
33
34
  "Show this help message.") { puts opts; exit }
35
+ opts.on('--aging',
36
+ "Show invoice aging info.") { aging = true }
37
+
34
38
  opts.parse!(ARGV)
35
39
 
36
40
  if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
@@ -38,6 +42,17 @@ BANNER
38
42
  end
39
43
  end
40
44
 
45
+ if aging
46
+ Freshtrack.init
47
+ aging = Freshtrack.invoice_aging
48
+ printf "%-10s\t%-50s\t%s\t%s\n", 'Invoice', 'Client', 'Age', 'Status'
49
+ printf "%s\n", '-' * 86
50
+ aging.each do |inv|
51
+ printf "%-10s\t%-50s\t%s\t%s\n", *inv.values_at(:number, :client, :age, :status)
52
+ end
53
+ exit
54
+ end
55
+
41
56
  project = ARGV.shift
42
57
 
43
58
  unless project
data/config/hoe.rb CHANGED
@@ -7,6 +7,13 @@ GEM_NAME = 'freshtrack' # what ppl will type to install your gem
7
7
  RUBYFORGE_PROJECT = 'yomendel' # The unix name for your project
8
8
  HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
9
  DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+ EXTRA_DEPENDENCIES = [
11
+ ['freshbooks', '= 2.1']
12
+ ] # An array of rubygem dependencies [name, version]
13
+ EXTRA_DEV_DEPENDENCIES = [
14
+ ['rspec', '>= 1.1.4'],
15
+ ['mocha', '>= 0.9.1']
16
+ ] # An array of rubygem dependencies [name, version]
10
17
 
11
18
  @config_file = "~/.rubyforge/user-config.yml"
12
19
  @config = nil
@@ -59,9 +66,8 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
59
66
 
60
67
  # == Optional
61
68
  p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
62
- p.extra_deps = [ # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
- ['freshbooks', '= 2.1']
64
- ]
69
+ p.extra_deps = EXTRA_DEPENDENCIES
70
+ p.extra_dev_deps = EXTRA_DEV_DEPENDENCIES
65
71
 
66
72
  #p.spec_extras = {} # A hash of extra values to set in the gemspec.
67
73
 
@@ -3,3 +3,4 @@ require 'freshbooks/extensions/base_object'
3
3
  require 'freshbooks/extensions/project'
4
4
  require 'freshbooks/extensions/task'
5
5
  require 'freshbooks/extensions/time_entry'
6
+ require 'freshbooks/extensions/invoice'
@@ -2,7 +2,7 @@ module Freshtrack #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 4
5
- TINY = 0
5
+ TINY = 1
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/lib/freshtrack.rb CHANGED
@@ -74,5 +74,22 @@ module Freshtrack
74
74
  klass = Freshtrack::TimeCollector.const_get(class_name)
75
75
  klass.new(options)
76
76
  end
77
+
78
+ def open_invoices
79
+ invoices = FreshBooks::Invoice.list || []
80
+ invoices.select { |i| i.open? }
81
+ end
82
+
83
+ def invoice_aging
84
+ open_invoices.collect do |i|
85
+ {
86
+ :id => i.invoice_id,
87
+ :number => i.number,
88
+ :client => i.client.organization,
89
+ :age => Date.today - i.date,
90
+ :status => i.status
91
+ }
92
+ end
93
+ end
77
94
  end
78
95
  end
@@ -71,4 +71,77 @@ describe 'freshtrack command' do
71
71
  Freshtrack.expects(:track).with(@project, {})
72
72
  run_command(@project)
73
73
  end
74
+
75
+ describe 'when --aging specified' do
76
+ before :each do
77
+ @aging_info = [
78
+ { :id => 5, :number => '123', :age => 31, :client => 'blah', :status => 'viewed' },
79
+ { :id => 53, :number => '234', :age => 43, :client => 'bang', :status => 'sent' },
80
+ { :id => 20, :number => '938', :age => 3, :client => 'boom', :status => 'viewed' }
81
+ ]
82
+ Freshtrack.stubs(:invoice_aging).returns(@aging_info)
83
+ self.stubs(:printf)
84
+ end
85
+
86
+ def aging_run
87
+ run_command('--aging')
88
+ end
89
+
90
+ it 'should not require a project' do
91
+ self.expects(:puts) { |text| text.match(/usage.+project/i) }.never
92
+ aging_run
93
+ end
94
+
95
+ it 'should init Freshtrack' do
96
+ Freshtrack.expects(:init)
97
+ aging_run
98
+ end
99
+
100
+ it 'should get the invoice aging information' do
101
+ Freshtrack.expects(:invoice_aging).returns(@aging_info)
102
+ aging_run
103
+ end
104
+
105
+ it 'should print the number of each invoice' do
106
+ pending 'making this actually test what it purports to'
107
+ @aging_info.each do |info|
108
+ self.expects(:printf) { |*args| args.include?(info[:number]) }
109
+ end
110
+ aging_run
111
+ end
112
+
113
+ it 'should print the client of each invoice' do
114
+ pending 'making this actually test what it purports to'
115
+ @aging_info.each do |info|
116
+ self.expects(:printf) { |*args| args.include?(info[:client]) }
117
+ end
118
+ aging_run
119
+ end
120
+
121
+ it 'should print the age of each invoice' do
122
+ pending 'making this actually test what it purports to'
123
+ @aging_info.each do |info|
124
+ self.expects(:printf) { |*args| args.include?(info[:age]) }
125
+ end
126
+ aging_run
127
+ end
128
+
129
+ it 'should print the status of each invoice' do
130
+ pending 'making this actually test what it purports to'
131
+ @aging_info.each do |info|
132
+ self.expects(:printf) { |*args| args.include?(info[:status]) }
133
+ end
134
+ aging_run
135
+ end
136
+
137
+ it 'should not track time' do
138
+ Freshtrack.expects(:track).never
139
+ aging_run
140
+ end
141
+
142
+ it 'should not track time even when given a project' do
143
+ Freshtrack.expects(:track).never
144
+ run_command('--aging', @project)
145
+ end
146
+ end
74
147
  end
@@ -396,4 +396,92 @@ describe Freshtrack do
396
396
  end
397
397
  end
398
398
  end
399
+
400
+ it 'should list open invoices' do
401
+ Freshtrack.should respond_to(:open_invoices)
402
+ end
403
+
404
+ describe 'listing open invoices' do
405
+ before :each do
406
+ @invoices = Array.new(5) { stub('invoice', :open? => false) }
407
+ FreshBooks::Invoice.stubs(:list).returns(@invoices)
408
+ end
409
+
410
+ it 'should get a list of invoices' do
411
+ FreshBooks::Invoice.expects(:list).returns(@invoices)
412
+ Freshtrack.open_invoices
413
+ end
414
+
415
+ it 'should return only the open invoices' do
416
+ open_invoices = @invoices.values_at(0,1,2)
417
+ open_invoices.each { |i| i.stubs(:open?).returns(true) }
418
+
419
+ Freshtrack.open_invoices.should == open_invoices
420
+ end
421
+
422
+ it 'should return an empty array if there are no open invoices' do
423
+ Freshtrack.open_invoices.should == []
424
+ end
425
+
426
+ it 'should return an empty array if there are no invoices' do
427
+ FreshBooks::Invoice.stubs(:list).returns([])
428
+ Freshtrack.open_invoices.should == []
429
+ end
430
+
431
+ it 'should return an empty array if the invoice list returns nil' do
432
+ FreshBooks::Invoice.stubs(:list).returns(nil)
433
+ Freshtrack.open_invoices.should == []
434
+ end
435
+ end
436
+
437
+ it 'should show invoice aging' do
438
+ Freshtrack.should respond_to(:invoice_aging)
439
+ end
440
+
441
+ describe 'showing invoice aging' do
442
+ before :each do
443
+ today = Date.today
444
+
445
+ @invoices = [
446
+ stub('invoice', :invoice_id => '1234', :number => '4567', :client => stub('client', :organization => 'client 20'), :date => today - 3, :status => 'partial'),
447
+ stub('invoice', :invoice_id => '19873', :number => '1456', :client => stub('client', :organization => 'client 3'), :date => today - 20, :status => 'viewed'),
448
+ stub('invoice', :invoice_id => '0038', :number => '30267', :client => stub('client', :organization => 'client 4'), :date => today - 59, :status => 'sent')
449
+ ]
450
+ Freshtrack.stubs(:open_invoices).returns(@invoices)
451
+ end
452
+
453
+ it 'should get open invoices' do
454
+ Freshtrack.expects(:open_invoices).returns(@invoices)
455
+ Freshtrack.invoice_aging
456
+ end
457
+
458
+ it 'should extract the ID for each open invoice' do
459
+ ids = @invoices.collect { |i| i.invoice_id }
460
+ Freshtrack.invoice_aging.collect { |i| i[:id] }.should == ids
461
+ end
462
+
463
+ it 'should extract the number for each open invoice' do
464
+ numbers = @invoices.collect { |i| i.number }
465
+ Freshtrack.invoice_aging.collect { |i| i[:number] }.should == numbers
466
+ end
467
+
468
+ it 'should extract the client for each open invoice' do
469
+ clients = @invoices.collect { |i| i.client.organization }
470
+ Freshtrack.invoice_aging.collect { |i| i[:client] }.should == clients
471
+ end
472
+
473
+ it 'should extract the age for each open invoice' do
474
+ Freshtrack.invoice_aging.collect { |i| i[:age] }.should == [3, 20, 59]
475
+ end
476
+
477
+ it 'should extract the status for each open invoice' do
478
+ statuses = @invoices.collect { |i| i.status }
479
+ Freshtrack.invoice_aging.collect { |i| i[:status] }.should == statuses
480
+ end
481
+
482
+ it 'should return an empty array if there are no open invoices' do
483
+ Freshtrack.stubs(:open_invoices).returns([])
484
+ Freshtrack.invoice_aging.should == []
485
+ end
486
+ end
399
487
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: freshtrack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yossef Mendelssohn
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-29 00:00:00 -05:00
12
+ date: 2009-07-31 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,6 +22,26 @@ dependencies:
22
22
  - !ruby/object:Gem::Version
23
23
  version: "2.1"
24
24
  version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.4
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.9.1
44
+ version:
25
45
  - !ruby/object:Gem::Dependency
26
46
  name: hoe
27
47
  type: :development
@@ -30,7 +50,7 @@ dependencies:
30
50
  requirements:
31
51
  - - ">="
32
52
  - !ruby/object:Gem::Version
33
- version: 1.7.0
53
+ version: 2.3.2
34
54
  version:
35
55
  description: Track your time on FreshBooks
36
56
  email: ymendel@pobox.com
@@ -89,6 +109,8 @@ files:
89
109
  - tasks/website.rake
90
110
  has_rdoc: true
91
111
  homepage: http://yomendel.rubyforge.org
112
+ licenses: []
113
+
92
114
  post_install_message:
93
115
  rdoc_options:
94
116
  - --main
@@ -110,9 +132,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
132
  requirements: []
111
133
 
112
134
  rubyforge_project: yomendel
113
- rubygems_version: 1.2.0
135
+ rubygems_version: 1.3.5
114
136
  signing_key:
115
- specification_version: 2
137
+ specification_version: 3
116
138
  summary: Track your time on FreshBooks
117
139
  test_files: []
118
140