invoices 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ # About
2
+ My first gem. Also my first offline foray into OOB.
3
+
4
+ ### Installation
5
+ <code>$ gem install invoices</code>
6
+
7
+ ### Configuration
8
+ <code>$ invoices biller -n</code>
9
+ <code>$ invoices client -n</code>
10
+ <code>$ invoices invoice -c "Client Name"</code>
11
+
12
+ ### License
13
+ The MIT License (MIT)
14
+ Copyright (c) 2013 Aaron Macy (aaronmacy.com)
15
+
16
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ ## TO DO LIST
23
+ - Verify that there are records in the database
24
+ - Write Controller tests
25
+ - Prevent invalid data from being saved
26
+ - Improve CLI
27
+ - Validate data before stored to db
28
+ - Support MySQL & PostgreSQL
29
+ - Add custom error messages throughout (see comments)
30
+ - Client names need to be in quotes (raise exception if they aren't)
31
+ - Allow commit messages to be > 40 characters
32
+ - Universalize testing of #project_root
33
+ - [Review sqlite3 gem methods](http://sqlite-ruby.rubyforge.org/sqlite3/)
34
+ - Allow multiple git repos per invoice
35
+ - Provide control over which commits get added to the invoice
36
+ - Add ability to regenerate invoices
37
+ - Add ability to preview invoices @ the command line
38
+ - Allow users to select where they want their invoices to be stored
39
+ - Export invoices as PDFs
40
+
41
+ ### Bugs
@@ -0,0 +1,5 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.pattern = "spec/*_spec.rb"
5
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'sqlite3'
3
+ require_relative '../lib/invoices/controllers/application_controller'
4
+
5
+ app = ApplicationController.new
6
+ # Command Line Prompts
7
+ app.parse_options
8
+ app.parse_commands
@@ -0,0 +1,68 @@
1
+ class Schema
2
+ def initalize
3
+ INVOICES_DB
4
+ TEST_DB
5
+ end
6
+ def create_all_tables(db)
7
+ begin
8
+ create_billers_table(db)
9
+ create_clients_table(db)
10
+ create_invoices_table(db)
11
+ create_line_items_table(db)
12
+ rescue SQLite3::SQLException
13
+ end
14
+ end
15
+ def create_billers_table(db)
16
+ db.execute <<-SQL
17
+ create table billers (
18
+ name varchar(30),
19
+ street1 varchar(30),
20
+ street2 varchar(30),
21
+ city varchar(30),
22
+ state varchar(2),
23
+ zip varchar(5),
24
+ phone varchar(14),
25
+ email varchar(30)
26
+ );
27
+ SQL
28
+ end
29
+ def create_clients_table(db)
30
+ db.execute <<-SQL
31
+ create table clients (
32
+ name varchar(30),
33
+ street1 varchar(30),
34
+ street2 varchar(30),
35
+ city varchar(30),
36
+ state varchar(2),
37
+ zip varchar(5),
38
+ phone varchar(14),
39
+ email varchar(30),
40
+ rate int
41
+ );
42
+ SQL
43
+ end
44
+ def create_invoices_table(db)
45
+ db.execute <<-SQL
46
+ create table invoices (
47
+ invoice_number int,
48
+ date varchar(10),
49
+ client_id int,
50
+ total_hrs int,
51
+ total_cost int
52
+ );
53
+ SQL
54
+ end
55
+ def create_line_items_table(db)
56
+ db.execute <<-SQL
57
+ create table line_items (
58
+ invoice_number int,
59
+ line_number int,
60
+ commit_date varchar(10),
61
+ commit_msg varchar,
62
+ hrs int,
63
+ rate int,
64
+ cost int
65
+ );
66
+ SQL
67
+ end
68
+ end
@@ -0,0 +1,73 @@
1
+ require 'optparse'
2
+ require_relative '../global'
3
+ require_relative 'invoices_controller'
4
+ require_relative 'billers_controller'
5
+ require_relative 'clients_controller'
6
+ require_relative 'line_items_controller'
7
+ require_relative 'commits_controller'
8
+ require_relative '../models/invoice'
9
+ require_relative '../models/biller'
10
+ require_relative '../models/client'
11
+ require_relative '../models/line_item'
12
+ require_relative '../models/commit'
13
+ require_relative '../views/invoices_view'
14
+ require_relative '../../../db/schema'
15
+
16
+ class ApplicationController
17
+ def initialize
18
+ Schema.new.create_all_tables(INVOICES_DB)
19
+ Dir.mkdir(INVOICES_FOLDER) unless File.directory?(INVOICES_FOLDER)
20
+ end
21
+ def parse_options
22
+ options = {}
23
+ subcommand_help = "\nExamples:\nCreate an invoice: invoices invoice -c 'Client Name'\nCreate a biller: invoices biller -n\nCreate a client: invoices client -n"
24
+ @global = OptionParser.new do |opt|
25
+ opt.banner = "Usage: invoices options [subcommand [options]]"
26
+ opt.on("-v", "--version", "Check the version of Invoices") do# |v|
27
+ #options[:version] = v
28
+ puts "v#{INVOICES_VERSION}"
29
+ end
30
+ opt.on("-h", "--help", "Get some help") do# |v|
31
+ #options[:help] = v
32
+ puts @global
33
+ puts subcommand_help
34
+ end
35
+ end
36
+ subcommands = {
37
+ 'invoice' => OptionParser.new do |opt|
38
+ opt.on("-c", "--client CLIENT", "Select the client for this invoice") do |v|
39
+ #option[:client] = v
40
+ client = Client.new.find_by_name(v)
41
+ InvoicesController.new(client)
42
+ end
43
+ end,
44
+ 'biller' => OptionParser.new do |opt|
45
+ opt.on("-n", "--new", "Add a new biller to the database") do# |v|
46
+ #option[:biller_new] = v
47
+ BillersController.new
48
+ end
49
+ end,
50
+ 'client' => OptionParser.new do |opt|
51
+ opt.on("-n", "--new", "Add a new client to the database") do# |v|
52
+ #option[:client_new] = v
53
+ ClientsController.new
54
+ end
55
+ end
56
+ }
57
+ @global.order!
58
+ cmd = ARGV.shift
59
+ if cmd #&& subcommands.key?(cmd.to_sym)
60
+ subcommands[cmd].order!
61
+ else
62
+ puts @global
63
+ puts subcommand_help
64
+ end
65
+ end
66
+ def parse_commands
67
+ case ARGV[0]
68
+ when "invoice"
69
+ when "biller"
70
+ when "client"
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,22 @@
1
+ class BillersController
2
+ def initialize
3
+ biller = Biller.new
4
+ puts "your name >"
5
+ biller.name = $stdin.gets.chomp
6
+ puts "street1 >"
7
+ biller.street1 = $stdin.gets.chomp
8
+ puts "street2 >"
9
+ biller.street2 = $stdin.gets.chomp
10
+ puts "city >"
11
+ biller.city = $stdin.gets.chomp
12
+ puts "state (2 letters) >"
13
+ biller.state = $stdin.gets.chomp
14
+ puts "zip (5 digits) >"
15
+ biller.zip = $stdin.gets.chomp
16
+ puts "phone >"
17
+ biller.phone = $stdin.gets.chomp
18
+ puts "email >"
19
+ biller.email = $stdin.gets.chomp
20
+ biller.save
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ class ClientsController
2
+ def initialize
3
+ client = Client.new
4
+ puts "client name >"
5
+ client.name = $stdin.gets.chomp
6
+ puts "street1 >"
7
+ client.street1 = $stdin.gets.chomp
8
+ puts "street2 >"
9
+ client.street2 = $stdin.gets.chomp
10
+ puts "city >"
11
+ client.city = $stdin.gets.chomp
12
+ puts "state (2 letters) >"
13
+ client.state = $stdin.gets.chomp
14
+ puts "zip (5 digits) >"
15
+ client.zip = $stdin.gets.chomp
16
+ puts "phone >"
17
+ client.phone = $stdin.gets.chomp
18
+ puts "email >"
19
+ client.email = $stdin.gets.chomp
20
+ puts "hourly rate you'll charge this client >"
21
+ client.rate = $stdin.gets.chomp
22
+ client.save
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ class CommitsController
2
+ def initialize(raw_commits)
3
+ @raw_commits = raw_commits
4
+ end
5
+ def index
6
+ @raw_commits.map do |line|
7
+ Commit.new(line)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ class InvoicesController
2
+ def initialize(client)
3
+ @biller = Biller.new.default
4
+ @invoice = Invoice.new
5
+ @client = client
6
+ @invoice.client_id = @client.id
7
+ get_root
8
+ add_line_items
9
+ create_file
10
+ end
11
+ def get_root
12
+ puts "Where is the project root (the parent directory of the git repo)?"
13
+ @invoice.project_root($stdin.gets.chomp)
14
+ @invoice.git_root
15
+ end
16
+ def add_line_items
17
+ commits = CommitsController.new(@invoice.git_log)
18
+ LineItemsController.new(@invoice, commits.index, @client)
19
+ end
20
+ def create_file
21
+ @invoice.save
22
+ view = InvoicesView.new(@invoice, @biller, @client)
23
+ File.open("#{INVOICES_FOLDER}/invoice#{@invoice.format_number}.txt", 'w') { |f| f.write(view.render) }
24
+ puts "generated invoice#{@invoice.format_number}.txt"
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ class LineItemsController
2
+ def initialize(invoice, commits, client)
3
+ @invoice, @commits_index, @client = invoice, commits, client
4
+ compile_line_items
5
+ end
6
+ def compile_line_items
7
+ puts "Would you like to enter a different rate for each commit? (y/n)"
8
+ custom_rate = true if $stdin.gets.chomp == "y"
9
+ i = 0
10
+ line_item = nil
11
+ @commits_index.each do |commit|
12
+ puts "\ncommit #{i + 1}: " + commit.msg
13
+ puts "how long did this take?"
14
+ item_hrs = $stdin.gets.chomp
15
+ if custom_rate
16
+ puts "how much will you charge?"
17
+ line_item = LineItem.new(@invoice.number, i + 1, commit.date, commit.msg, item_hrs, $stdin.gets.chomp)
18
+ else
19
+ line_item = LineItem.new(@invoice.number, i + 1, commit.date, commit.msg, item_hrs, @client.rate)
20
+ end
21
+ line_item.save
22
+ @invoice.add_line_item(line_item)
23
+ i += 1
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ INVOICES_VERSION = '0.1.0'
2
+ INVOICES_FOLDER = File.expand_path('~/Invoices')
3
+ INVOICES_DB = SQLite3::Database.new('db/invoices.db')
4
+ TEST_DB = SQLite3::Database.new('db/test.db')
5
+
6
+ def choose_db(*boolean)
7
+ if boolean.first
8
+ TEST_DB
9
+ else
10
+ INVOICES_DB
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ class Biller
2
+ attr_accessor :name, :street1, :street2, :city,
3
+ :state, :zip, :phone, :email
4
+ def default(*boolean)
5
+ biller = choose_db(*boolean).execute("select * from billers").first
6
+ @name = biller[0].to_s
7
+ @street1 = biller[1].to_s
8
+ @street2 = biller[2].to_s
9
+ @city = biller[3].to_s
10
+ @state = biller[4].to_s
11
+ @zip = biller[5].to_s
12
+ @phone = biller[6].to_s
13
+ @email = biller[7].to_s
14
+ return self
15
+ end
16
+ def save(*boolean)
17
+ # Should raise error unless all fields except for street2 are filled
18
+ choose_db(*boolean).execute("INSERT INTO billers
19
+ (name, street1, street2, city, state, zip, phone, email)
20
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
21
+ [@name, @street1, @street2, @city, @state, @zip, @phone, @email])
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ class Client
2
+ attr_accessor :id, :name, :street1, :street2, :city,
3
+ :state, :zip, :phone, :rate, :email
4
+ def find_by_name(name, *boolean)
5
+ client = choose_db(*boolean).execute("select * from clients
6
+ where name = '#{name}'").first
7
+ @id = choose_db(*boolean).execute("select rowid from clients
8
+ where name = '#{name}'").first
9
+ @name = client[0].to_s
10
+ @street1 = client[1].to_s
11
+ @street2 = client[2].to_s
12
+ @city = client[3].to_s
13
+ @state = client[4].to_s
14
+ @zip = client[5].to_s
15
+ @phone = client[6].to_s
16
+ @email = client[7].to_s
17
+ @rate = client[8]
18
+ return self
19
+ end
20
+ def save(*boolean)
21
+ choose_db(*boolean).execute("INSERT INTO clients
22
+ (name, street1, street2, city, state,
23
+ zip, phone, email, rate)
24
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
25
+ [@name, @street1, @street2, @city, @state, @zip, @phone, @email, @rate])
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ class Commit
2
+ attr_accessor :date, :msg
3
+ def initialize(line)
4
+ parse_date(line)
5
+ parse_msg(line)
6
+ end
7
+ def parse_date(line)
8
+ timestamp = line.split(/> /).last.slice(0, 10).to_i
9
+ @date = Time.at(timestamp) # Convert Unix timestamp
10
+ end
11
+ def parse_msg(line)
12
+ if line.include?("commit:")
13
+ @msg = line.split(/commit:/).last.strip.slice(0, 40)
14
+ elsif line.include?("commit (initial):")
15
+ @msg = line.split(/commit \(initial\):/).last.strip.slice(0, 40)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,75 @@
1
+ class Invoice
2
+ attr_reader :hours, :rate, :format_number, :line_items_array, :root, :git_log
3
+ attr_accessor :client_id, :total_hrs, :total_cost, :number, :date
4
+ def initialize
5
+ calculate_number
6
+ @line_items_array = []
7
+ @git_log = []
8
+ @total_hrs = 0
9
+ @total_cost = 0
10
+ @date = Time.now.strftime("%m/%d/%y")
11
+ end
12
+ def calculate_number
13
+ def format(x)
14
+ if x >= 1 && x < 10 then "000#{x}"
15
+ elsif x >= 10 && x < 100 then "00#{x}"
16
+ elsif x >= 100 && x < 1000 then "0#{x}"
17
+ elsif x >= 100 && x < 10000 then "#{x}"
18
+ # raise an exception for x >= 10000
19
+ end
20
+ end
21
+ i = 1
22
+ Dir.foreach(File.expand_path('~/invoices')) do |filename|
23
+ if filename.include?("invoice#{format(i)}.txt")
24
+ i += 1
25
+ else
26
+ format(i)
27
+ end
28
+ end
29
+ @number = i
30
+ @format_number = format(i)
31
+ end
32
+ def project_root(file)
33
+ @root = File.expand_path(file)
34
+ end
35
+ def git_root
36
+ File.open("#{@root}/.git/logs/HEAD", "r") do |file|
37
+ file.each_line do |line|
38
+ @git_log << line if line.include?("commit")
39
+ end
40
+ end
41
+ end
42
+ def calculate_total_hrs
43
+ @line_items_array.each do |line_item|
44
+ @total_hrs += line_item.hrs
45
+ end
46
+ @total_hrs
47
+ end
48
+ def calculate_total_cost
49
+ @line_items_array.each do |line_item|
50
+ @total_cost += line_item.cost
51
+ end
52
+ @total_cost
53
+ end
54
+ def save(*boolean)
55
+ calculate_total_hrs
56
+ calculate_total_cost
57
+ choose_db(*boolean).execute("INSERT INTO invoices
58
+ (invoice_number, date, client_id, total_hrs, total_cost)
59
+ VALUES (?, ?, ?, ?, ?)",
60
+ [@number, @date, @client_id, @total_hrs, @total_cost])
61
+ end
62
+ def add_line_item(line_item)
63
+ @line_items_array << line_item
64
+ end
65
+ def find_by_invoice_number(invoice_number, *boolean)
66
+ invoice = choose_db(*boolean).execute("select * from invoices where
67
+ invoice_number = #{invoice_number}").first
68
+ @number = invoice[0]
69
+ @date = invoice[1]
70
+ @client_id = invoice[2]
71
+ @total_hrs = invoice[3]
72
+ @total_cost = invoice[4]
73
+ return self
74
+ end
75
+ end
@@ -0,0 +1,27 @@
1
+ class LineItem
2
+ attr_accessor :invoice_number, :line_number, :date,
3
+ :msg, :hrs, :rate, :cost
4
+ def initialize(invoice_number, line_number, date, msg, hrs, rate)
5
+ @invoice_number = invoice_number
6
+ @line_number = line_number
7
+ @date = date.to_s
8
+ @msg = msg
9
+ @hrs = hrs.to_i
10
+ @rate = rate.to_i
11
+ @cost = @hrs * @rate
12
+ end
13
+ def find_by_invoice_number(invoice_number, *boolean)
14
+ items = choose_db(*boolean).execute("select * from line_items where
15
+ invoice_number = #{invoice_number}")
16
+ items.map! do |line|
17
+ LineItem.new(line[0], line[1], line[2], line[3], line[4].to_s, line[5].to_s)
18
+ end
19
+ items
20
+ end
21
+ def save(*boolean)
22
+ choose_db(*boolean).execute("INSERT INTO line_items
23
+ (invoice_number, line_number, commit_date, commit_msg, hrs, rate, cost)
24
+ VALUES (?, ?, ?, ?, ?, ?, ?)",
25
+ [@invoice_number, @line_number, @date, @msg, @hrs, @rate, @cost])
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ module ViewsHelpers
2
+ def compare_length(string, max_length)
3
+ # raise error if string.length < 0 || string.length > max_length
4
+ string = string.to_s unless string.instance_of?(String)
5
+ if string.length < max_length
6
+ difference = 0
7
+ difference = max_length - string.length
8
+ string + (" " * difference)
9
+ else
10
+ string
11
+ end
12
+ end
13
+ def format_hrs(h)
14
+ compare_length(h, 3)
15
+ end
16
+ def format_rate(amt)
17
+ compare_length("$" + amt.to_s, 4)
18
+ end
19
+ def divider
20
+ " | "
21
+ end
22
+ end
@@ -0,0 +1,78 @@
1
+ require 'time'
2
+ require_relative 'helpers/views_helper'
3
+
4
+ class InvoicesView
5
+ include ViewsHelpers
6
+ def initialize(invoice, biller, client)
7
+ @invoice, @biller, @client = invoice, biller, client
8
+ end
9
+ def header
10
+ def space(chars)
11
+ " " * (72 - chars) # 72 chars in page width was,
12
+ end # traditionally, the most common
13
+ def line(left, right)
14
+ s = space(left.length + right.length)
15
+ l = left + s + right + "\n"
16
+ if l.strip.empty?
17
+ ""
18
+ else
19
+ l
20
+ end
21
+ end
22
+ def address(person)
23
+ line(person.name, " ") +
24
+ line(person.street1, " ") +
25
+ line(person.street2, " ") +
26
+ line(person.city + ", " + person.state + " " + person.zip, " ") +
27
+ line(person.phone, " ") +
28
+ line(person.email, " ")
29
+ end
30
+ line("INVOICE #" + @invoice.format_number, @invoice.date) + "\n" +
31
+ address(@biller) + "\n" * 2 +
32
+ line("BILL TO:", "") + address(@client) + "\n" * 2
33
+ end
34
+ def grid
35
+ def border_top
36
+ "----+-- DATE --+------------- COMMIT MESSAGE -------------+ HRS + RATE +" +
37
+ ("\n" * 2)
38
+ end
39
+ def border_bottom
40
+ ("\n" * 2) +
41
+ "----+----------+------------------------------------------+-----+------+" + "\n"
42
+ end
43
+ def total
44
+ "TOTALS:" + (" " * 53) + "#{format_hrs(@invoice.total_hrs)}" +
45
+ divider + "#{format_rate(@invoice.total_cost)}" + "\n"
46
+ end
47
+ border_top + LineItemsView.new.prepare(@invoice.line_items_array).join("\n") +
48
+ border_bottom + total
49
+ end
50
+ def render
51
+ header + grid
52
+ end
53
+ end
54
+
55
+ class LineItemsView
56
+ include ViewsHelpers
57
+ def format_number(n)
58
+ compare_length(n, 3)
59
+ end
60
+ def format_date(d)
61
+ d = Time.parse(d)
62
+ d = d.strftime("%m/%d/%y")
63
+ compare_length(d, 8)
64
+ end
65
+ def format_msg(m)
66
+ compare_length(m, 40)
67
+ end
68
+ def prepare(line_items)
69
+ # Receives an array from LineItemsController
70
+ line_items.map do |item|
71
+ format_number(item.line_number) + divider +
72
+ format_date(item.date) + divider +
73
+ format_msg(item.msg) + divider +
74
+ format_hrs(item.hrs) + divider +
75
+ format_rate(item.rate)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'spec_helper'
2
+ require_relative '../lib/invoices/controllers/application_controller'
3
+ require_relative '../lib/invoices/controllers/invoices_controller'
4
+ require_relative '../lib/invoices/controllers/billers_controller'
5
+ require_relative '../lib/invoices/controllers/clients_controller'
6
+ require_relative '../lib/invoices/controllers/line_items_controller'
7
+ require_relative '../lib/invoices/controllers/commits_controller'
8
+ =begin
9
+ describe ApplicationController do
10
+ before do
11
+ @app = ApplicationController.new
12
+ end
13
+
14
+ describe "#initalize" do
15
+ it "should create a db & invoice folder" do
16
+ File.directory?(INVOICES_FOLDER).must_equal true
17
+ end
18
+
19
+ it "should create invoices.db" do
20
+ File.exists?(INVOICES_DB).must_equal true
21
+ end
22
+
23
+ it "should create test.db" do
24
+ File.exists?(TEST_DB).must_equal true
25
+ end
26
+ end
27
+
28
+ #describe "#parse_options" do
29
+ #end
30
+
31
+ #describe "#parse_commands" do
32
+ #end
33
+ end
34
+
35
+ describe InvoicesController do
36
+ before do
37
+ @invoices_controller = InvoicesController.new(Client.new)
38
+ end
39
+
40
+ describe "#initialize" do
41
+ it "should set some instance variables" do
42
+ @invoices_controller.biller.wont_be_nil
43
+ @invoices_controller.biller.must_be_instance_of Biller
44
+ @invoices_controller.invoice.must_be_instance_of Invoice
45
+ end
46
+ end
47
+ end
48
+
49
+ describe BillersController do
50
+ end
51
+
52
+ describe ClientsController do
53
+ end
54
+
55
+ describe LineItemsController do
56
+ end
57
+
58
+ describe CommitsController do
59
+ end
60
+ =end
@@ -0,0 +1,237 @@
1
+ require_relative 'spec_helper'
2
+ require_relative '../lib/invoices/models/invoice'
3
+ require_relative '../lib/invoices/models/biller'
4
+ require_relative '../lib/invoices/models/client'
5
+ require_relative '../lib/invoices/models/line_item'
6
+ require_relative '../lib/invoices/models/commit'
7
+
8
+ describe Invoice do
9
+ before do
10
+ TEST_DB.execute("DELETE FROM billers") # Combining into 1 SQL command
11
+ TEST_DB.execute("DELETE FROM clients") # was not working
12
+ TEST_DB.execute("DELETE FROM invoices")
13
+ TEST_DB.execute("DELETE FROM line_items")
14
+ @invoice = Invoice.new
15
+ end
16
+
17
+ describe "#initialize" do
18
+ it "should set some instance variables" do
19
+ @invoice.number.must_be :>, 0 # Also tests #calculate_number
20
+ @invoice.format_number.must_be_instance_of String
21
+ @invoice.date.must_equal Time.now.strftime("%m/%d/%y")
22
+ @invoice.git_log.must_be_instance_of Array
23
+ @invoice.line_items_array.must_be_instance_of Array
24
+ @invoice.total_hrs.must_equal 0
25
+ @invoice.total_cost.must_equal 0
26
+ end
27
+ end
28
+
29
+ describe "#project_root" do
30
+ before do
31
+ @invoice.project_root('~') # Universalize
32
+ end
33
+
34
+ it "should set the file path" do
35
+ @invoice.root.must_equal File.expand_path('~')
36
+ end
37
+
38
+ describe "#git_root" do
39
+ before do
40
+ @invoice.git_root
41
+ end
42
+
43
+ specify { @invoice.git_log.must_be_instance_of Array }
44
+ specify { @invoice.git_log.each { |line| line.must_include "commit" }}
45
+
46
+ describe "#add_line_item" do
47
+ before do
48
+ item1 = LineItem.new(@invoice.number, 1, @invoice.date, "Lorem ipsum", 7, 20)
49
+ item2 = LineItem.new(@invoice.number, 2, @invoice.date, "Lipsum", 5, 15)
50
+ @invoice.add_line_item(item1)
51
+ @invoice.add_line_item(item2)
52
+ end
53
+
54
+ it "should create an array of LineItem objects" do
55
+ @invoice.line_items_array[0].must_be_instance_of LineItem
56
+ end
57
+
58
+ describe "#save" do
59
+ before do
60
+ @invoice.client_id = 3
61
+ @invoice.save(true)
62
+ end
63
+
64
+ it "should not save empty data" do
65
+ @invoice.number.must_be :>, 0
66
+ @invoice.date.wont_be_empty
67
+ @invoice.client_id.must_be :>, 0
68
+ @invoice.total_hrs.must_be :>, 0
69
+ @invoice.total_cost.must_be :>, 0
70
+ end
71
+
72
+ it "should raise an error for invalid data"
73
+
74
+ it "should call #calculate_total_hrs" do
75
+ @invoice.total_hrs.must_equal 12
76
+ end
77
+
78
+ it "should call #calculate_total_cost" do
79
+ @invoice.total_cost.must_equal 215
80
+ end
81
+
82
+ describe "#find_by_invoice_number" do
83
+ before do
84
+ @invoice_query = Invoice.new.find_by_invoice_number(@invoice.number, true)
85
+ @invoice_query.must_be_instance_of Invoice
86
+ end
87
+
88
+ it "should set instance variables from the database" do
89
+ @invoice.number.must_equal @invoice_query.number
90
+ @invoice.date.must_equal @invoice_query.date
91
+ @invoice.client_id.must_equal @invoice_query.client_id
92
+ @invoice.total_hrs.must_equal @invoice_query.total_hrs
93
+ @invoice.total_cost.must_equal @invoice_query.total_cost
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ describe Biller do
103
+ before do
104
+ @biller = Biller.new
105
+ @biller.name = "Aaron Burr"
106
+ @biller.street1 = "VP"
107
+ @biller.street2 = ""
108
+ @biller.city = "Washington"
109
+ @biller.state = "DC"
110
+ @biller.zip = "12345"
111
+ @biller.phone = "Telegram"
112
+ @biller.email = "aburr@example.com"
113
+ end
114
+
115
+ describe "#save" do
116
+ before do
117
+ @biller.save(true)
118
+ end
119
+
120
+ describe "#default" do # Write a test for the if/else
121
+ before do
122
+ @biller_query = Biller.new.default(true)
123
+ @biller_query.must_be_instance_of Biller
124
+ end
125
+
126
+ it "should set instance variables from the database" do
127
+ @biller.name.must_equal @biller_query.name
128
+ @biller.street1.must_equal @biller_query.street1
129
+ @biller.street2.must_equal @biller_query.street2
130
+ @biller.city.must_equal @biller_query.city
131
+ @biller.state.must_equal @biller_query.state
132
+ @biller.zip.must_equal @biller_query.zip
133
+ @biller.phone.must_equal @biller_query.phone
134
+ @biller.email.must_equal @biller_query.email
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ describe Client do
141
+ before do
142
+ @client = Client.new
143
+ @client.name = "Alexander Hamilton"
144
+ @client.street1 = "Wall St"
145
+ @client.street2 = ""
146
+ @client.city = "Dover"
147
+ @client.state = "DE"
148
+ @client.zip = "55555"
149
+ @client.phone = "555-555-5555"
150
+ @client.rate = 100
151
+ @client.email = "ah@example.com"
152
+ end
153
+
154
+ describe "#save" do
155
+ before do
156
+ @client.save(true)
157
+ end
158
+ # Write a test for #all & the if/else
159
+ describe "#find_by_name" do
160
+ before do
161
+ @client_query = Client.new.find_by_name("Alexander Hamilton", true)
162
+ @client_query.must_be_instance_of Client
163
+ end
164
+
165
+ it "should set instance variables from the database" do
166
+ @client.name.must_equal @client_query.name
167
+ @client.street1.must_equal @client_query.street1
168
+ @client.street2.must_equal @client_query.street2
169
+ @client.city.must_equal @client_query.city
170
+ @client.state.must_equal @client_query.state
171
+ @client.zip.must_equal @client_query.zip
172
+ @client.phone.must_equal @client_query.phone
173
+ @client.email.must_equal @client_query.email
174
+ @client.rate.must_equal @client_query.rate
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ describe LineItem do
181
+ before do
182
+ @line_item1 = LineItem.new(7, 1, Time.now, "Lorem ipsum", 7, 20)
183
+ @line_item2 = LineItem.new(7, 2, Time.now, "Lipsum", 5, 15)
184
+ end
185
+
186
+ describe "#save" do
187
+ before do
188
+ @line_item1.save(true)
189
+ @line_item2.save(true)
190
+ end
191
+
192
+ describe "#find_by_invoice_number" do
193
+ before do
194
+ line_item_query = @line_item1.find_by_invoice_number(7, true)
195
+ @line1 = line_item_query[0]
196
+ @line2 = line_item_query[1]
197
+ end
198
+
199
+ it "should set instance variables from the database" do
200
+ @line_item1.invoice_number.must_equal @line1.invoice_number
201
+ @line_item1.line_number.must_equal @line1.line_number
202
+ @line_item1.date.must_equal @line1.date
203
+ @line_item1.msg.must_equal @line1.msg
204
+ @line_item1.hrs.must_equal @line1.hrs
205
+ @line_item1.rate.must_equal @line1.rate
206
+ @line_item1.cost.must_equal @line1.cost
207
+ @line_item2.invoice_number.must_equal @line2.invoice_number
208
+ @line_item2.line_number.must_equal @line2.line_number
209
+ @line_item2.date.must_equal @line2.date
210
+ @line_item2.msg.must_equal @line2.msg
211
+ @line_item2.hrs.must_equal @line2.hrs
212
+ @line_item2.rate.must_equal @line2.rate
213
+ @line_item2.cost.must_equal @line2.cost
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ describe Commit do
220
+ before do
221
+ line = "0000000000000000000000000000000000000000 847657c5752fb6de037f7ed1da964c702952867d Aaron Macy <aaronmacy@gmail.com> 1355558298 -0800 commit (initial): initial commit"
222
+ @commit = Commit.new(line)
223
+ end
224
+
225
+ describe "#parse_date" do
226
+ it "should convert the Unix timestamp to Time object" do
227
+ @commit.date.must_be_instance_of Time
228
+ end
229
+ end
230
+
231
+ describe "#parse_msg" do
232
+ it "should strip the commit message to a string of <= 40 chars" do
233
+ @commit.msg.must_be_instance_of String
234
+ @commit.msg.length.must_be :<=, 40
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ gem 'minitest'
3
+ require 'minitest/autorun'
4
+ require 'sqlite3'
5
+ require_relative '../db/schema'
6
+ require_relative '../lib/invoices/global'
7
+
8
+ Schema.new.create_all_tables(TEST_DB)
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: invoices
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Aaron Macy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! "Generate monospaced .txt invoices at the command line using\n Git
15
+ Commits."
16
+ email: aaronmacy@gmail.com
17
+ executables:
18
+ - invoices
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - lib/invoices/global.rb
23
+ - lib/invoices/controllers/application_controller.rb
24
+ - lib/invoices/controllers/billers_controller.rb
25
+ - lib/invoices/controllers/clients_controller.rb
26
+ - lib/invoices/controllers/commits_controller.rb
27
+ - lib/invoices/controllers/invoices_controller.rb
28
+ - lib/invoices/controllers/line_items_controller.rb
29
+ - lib/invoices/models/biller.rb
30
+ - lib/invoices/models/client.rb
31
+ - lib/invoices/models/commit.rb
32
+ - lib/invoices/models/invoice.rb
33
+ - lib/invoices/models/line_item.rb
34
+ - lib/invoices/views/helpers/views_helper.rb
35
+ - lib/invoices/views/invoices_view.rb
36
+ - bin/invoices
37
+ - Rakefile
38
+ - README.md
39
+ - db/schema.rb
40
+ - spec/controllers_spec.rb
41
+ - spec/models_spec.rb
42
+ - spec/spec_helper.rb
43
+ homepage: http://github.com/amacy/invoices
44
+ licenses:
45
+ - MIT
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements:
63
+ - sqlite3
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.24
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Generate invoices at the command line using Git Commits.
69
+ test_files: []