text-invoice 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in text-invoice.gemspec
4
+ gemspec
@@ -0,0 +1,66 @@
1
+ # Text Invoice
2
+
3
+ Some tools and helpers for creating, summarising and transforming invoices stored as [YAML][yaml] documents.
4
+
5
+ The tools are designed to be used in a UNIX-like environment.
6
+
7
+ ## Typical uses
8
+
9
+ Create a new invoice
10
+
11
+ text-invoice new > some-job.yaml
12
+
13
+ Update totals, transform to HTML and write to disk
14
+
15
+ cat some-job.yaml | text-invoice update | text-invoice html > some-job.html
16
+
17
+ Get a summary of all invoices
18
+
19
+ find -name "*.yaml" | xargs text-invoice summary
20
+
21
+ Get a summarised list of all invoices
22
+
23
+ find -name "*.yaml" | xargs text-invoice list
24
+
25
+ Get a summarised list of all invoices which contain a word
26
+
27
+ grep -r -l "some pattern" * | xargs text-invoice list
28
+
29
+ You get the idea.
30
+
31
+ ## Custom templates
32
+
33
+ It's just YAML so it's easy template. I use [Mustache templates][mustache] for the default HTML invoice, and added an option to support custom Mustache templates
34
+
35
+ cat some-job.yml | text-invoice template my-template.mustache > some-job.something
36
+
37
+ Of course if you have the Mustache gem installed (which is a dependency of this gem!) you can just use that!
38
+
39
+ cat some-job.yml | mustache - template.mustache > some-job.something
40
+
41
+ ## PDF
42
+
43
+ I transform my invoices in PDF using [wkhtmltopdf][wkhtmltopdf]. There is a [wkpdf gem][wkpdf] but it's only for Max OS X, so that wasn't getting included.
44
+
45
+ cat some-job.yaml | text-invoice update | text-invoice html > /tmp/invoice.html && wkhtmltopdf /tmp/invoice.html some-job.pdf
46
+
47
+ Use whatever makes you happy.
48
+
49
+ ## This seems stupid
50
+
51
+ I like having invoices in text so I can update them in another Vim buffer as I'm doing work. If I ever think I'm doing to much typing I can always wrap the commands in shell scripts or Vim mappings.
52
+
53
+ I prefer this to context switching to another application/website or trying to work out what I did from git logs.
54
+
55
+ ## Acknowledgements
56
+
57
+ The template I'm using is a modified version of the pretty-neat-but-not-what-I-wanted [Editable, Printable Invoice by Chris Coyer and others][editable-invoice].
58
+
59
+ This is the first gem I've written, and [this guide][gem-dev] was helpful.
60
+
61
+ [editable-invoice]: http://css-tricks.com/editable-invoice-v2/
62
+ [wkhtmltopdf]: http://code.google.com/p/wkhtmltopdf/
63
+ [mustache]: http://mustache.github.com/
64
+ [gem-dev]: https://github.com/radar/guides/blob/master/gem-development.md
65
+ [yaml]: http://yaml.org/
66
+ [wkpdf]: http://rubygems.org/gems/wkpdf
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require 'text-invoice/cli'
3
+ cli = TextInvoice::CLI.new
4
+ puts cli.run()
5
+
@@ -0,0 +1,11 @@
1
+ require "text-invoice/version"
2
+ require "text-invoice/totals"
3
+ require "text-invoice/summary"
4
+ require "text-invoice/cli"
5
+ require "text-invoice/tasks"
6
+ require "text-invoice/invoice"
7
+ require "text-invoice/template"
8
+ require 'yaml'
9
+
10
+ module TextInvoice
11
+ end
@@ -0,0 +1,37 @@
1
+ require 'text-invoice'
2
+
3
+ module TextInvoice
4
+ class CLI
5
+ attr_accessor :tasks, :argv, :stdin
6
+
7
+ def initialize()
8
+ @tasks = TextInvoice::Tasks.new
9
+ @argv = ARGV
10
+ @stdin = $stdin
11
+ end
12
+
13
+ def run()
14
+ mode = @argv.shift
15
+ if mode == "update"
16
+ @tasks.totals(@stdin.read)
17
+ elsif mode == "summary"
18
+ @tasks.summary(@argv)
19
+ elsif mode == "list"
20
+ @tasks.list(@argv)
21
+ elsif mode == "new"
22
+ @tasks.new_invoice()
23
+ elsif mode == "html"
24
+ @tasks.html(@stdin.read)
25
+ elsif mode == "template"
26
+ template = @argv.shift
27
+ if template
28
+ @tasks.template(@stdin.read, template)
29
+ else
30
+ @tasks.usage()
31
+ end
32
+ else
33
+ @tasks.usage()
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,13 @@
1
+ require 'text-invoice'
2
+
3
+ module TextInvoice
4
+ class Invoice
5
+ def self.blank()
6
+ open(root + "invoice.yaml").read
7
+ end
8
+
9
+ def self.root
10
+ File.dirname(__FILE__) + "/"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ ---
2
+ invoice:
3
+ date:
4
+ from:
5
+ name:
6
+ abn:
7
+ address1:
8
+ address2:
9
+ phone:
10
+ email:
11
+ to:
12
+ attn:
13
+ for:
14
+ name:
15
+ address1:
16
+ address2:
17
+ address3:
18
+ phone:
19
+ items:
20
+ - description: item
21
+ quantity: 0
22
+ unit: 0
23
+ paid: 0
24
+ terms:
25
+ ---
@@ -0,0 +1,34 @@
1
+ module TextInvoice
2
+ class Summary
3
+ def summary(invoices)
4
+ paid = 0
5
+ due = 0
6
+ total = 0
7
+ count = 0
8
+ invoices.each do |invoice|
9
+ data = load(invoice)
10
+ total += data["total"]
11
+ paid += data["paid"]
12
+ due += data["due"]
13
+ count += 1
14
+ end
15
+ headings = ["Count", "Total", "Paid", "Due"].join("\t")
16
+ results = [count, total, paid, due].join("\t")
17
+ [headings, results].join("\n")
18
+ end
19
+
20
+ def list(invoices)
21
+ response = [["Invoice","Date", "Total", "Paid", "Due"].join("\t")]
22
+ invoices.each do |invoice|
23
+ data = load(invoice)
24
+ response << ([data["invoice"], data["date"], data["total"], data["paid"], data["due"]].join("\t"))
25
+ end
26
+ response.join("\n")
27
+ end
28
+
29
+ def load(invoice)
30
+ calculator = TextInvoice::Totals.new
31
+ YAML.load(calculator.process(open(invoice)))
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ require 'text-invoice'
2
+
3
+ module TextInvoice
4
+ class Tasks
5
+ attr_accessor :totals_calculator, :summary_calculator, :invoice, :template
6
+
7
+ def initialize()
8
+ @totals_calculator = TextInvoice::Totals.new
9
+ @summary_calculator = TextInvoice::Summary.new
10
+ @invoice = TextInvoice::Invoice
11
+ @template = TextInvoice::Template.new
12
+ end
13
+
14
+ def totals(input)
15
+ @totals_calculator.process(input)
16
+ end
17
+
18
+ def summary(input)
19
+ @summary_calculator.summary(input)
20
+ end
21
+
22
+ def list(input)
23
+ @summary_calculator.list(input)
24
+ end
25
+
26
+ def new_invoice()
27
+ @invoice.blank()
28
+ end
29
+
30
+ def html(invoice)
31
+ @template.html(invoice)
32
+ end
33
+
34
+ def template(invoice, template)
35
+ @template.custom(invoice, template)
36
+ end
37
+
38
+ def usage()
39
+ text = """
40
+ Usage: text-invoice [new|totals|summary|list|html|template] [additional arguments..]
41
+
42
+ new Writes an empty invoice to STDOUT
43
+
44
+ update Calculates subtotals, totals and due amounts
45
+ Reads an invoice from STDIN and writes an updated invoice to STDOUT
46
+
47
+ summary Returns a summary of invoice files.
48
+ Summarises invoice files from additional arguments
49
+
50
+ list Returns a summarised list of invoices
51
+ Lists invoice files from additional arguments
52
+
53
+ html Transforms an invoice to HTML using a built-in template
54
+ Reads invoice from STDIN
55
+
56
+ template Transforms an invoice to HTML using a custom Mustache template
57
+ Reads invoice from STDIN. File path of a Mustache template must
58
+ be provided as an additional argument.
59
+ """
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ require 'text-invoice'
2
+ require 'mustache'
3
+
4
+ module TextInvoice
5
+ class Template
6
+ def html(invoice)
7
+ parsed = YAML.load(invoice)
8
+ Mustache.render(open(root + "templates/invoice.html").read(), parsed)
9
+ end
10
+
11
+ def custom(invoice, template)
12
+ parsed = YAML.load(invoice)
13
+ Mustache.render(open(template).read(), parsed)
14
+ end
15
+
16
+ def root
17
+ File.dirname(__FILE__) + "/../../"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ module TextInvoice
2
+ class Totals
3
+ def process(invoice)
4
+ # parse
5
+ parsed = YAML.load(invoice)
6
+
7
+ # total accumulator
8
+ total = 0
9
+
10
+ # process line items
11
+ if not parsed["items"] == nil
12
+ for item in parsed["items"]
13
+ quantity = item["quantity"]
14
+ unit = item["unit"]
15
+ subtotal = quantity * unit
16
+ item["subtotal"] = subtotal
17
+ total += subtotal
18
+ end
19
+ end
20
+
21
+ # update totals
22
+ parsed["total"] = total
23
+
24
+ if parsed["paid"]
25
+ parsed["due"] = total - parsed["paid"]
26
+ else
27
+ parsed["due"] = total
28
+ end
29
+
30
+ parsed.to_yaml
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module TextInvoice
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,150 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::CLI do
4
+ it "should return usage details with no parameters" do
5
+ tasks = double("tasks")
6
+ tasks.should_receive(:usage).and_return("usage details")
7
+
8
+ cli = TextInvoice::CLI.new
9
+ cli.tasks = tasks
10
+ cli.run().should eql("usage details")
11
+ end
12
+
13
+ it "should return usage details with unknow parameters" do
14
+ argv = double("argv")
15
+ argv.should_receive(:shift).and_return("something we don't do")
16
+
17
+ tasks = double("tasks")
18
+ tasks.should_receive(:usage).and_return("usage details")
19
+
20
+ cli = TextInvoice::CLI.new
21
+ cli.argv = argv
22
+ cli.tasks = tasks
23
+ cli.run().should eql("usage details")
24
+ end
25
+
26
+ it "should return summary task with argv parameter" do
27
+ argv = double("argv")
28
+ argv.should_receive(:shift).and_return("summary")
29
+
30
+ tasks = double("tasks")
31
+ tasks.should_receive(:summary).with(argv).and_return("summary results")
32
+
33
+ cli = TextInvoice::CLI.new
34
+ cli.tasks = tasks
35
+ cli.argv = argv
36
+
37
+ cli.run().should eql("summary results")
38
+ end
39
+
40
+ it "should return summary task with argv parameters" do
41
+ argv = double("argv")
42
+ argv.should_receive(:shift).and_return("summary")
43
+
44
+ tasks = double("tasks")
45
+ tasks.should_receive(:summary).with(argv).and_return("summary results")
46
+
47
+ cli = TextInvoice::CLI.new
48
+ cli.tasks = tasks
49
+ cli.argv = argv
50
+
51
+ cli.run().should eql("summary results")
52
+ end
53
+
54
+ it "should return list task with argv parameter" do
55
+ argv = double("argv")
56
+ argv.should_receive(:shift).and_return("list")
57
+
58
+ tasks = double("tasks")
59
+ tasks.should_receive(:list).with(argv).and_return("list results")
60
+
61
+ cli = TextInvoice::CLI.new
62
+ cli.tasks = tasks
63
+ cli.argv = argv
64
+
65
+ cli.run().should eql("list results")
66
+ end
67
+
68
+ it "should return totals task with stdin parameter" do
69
+ argv = double("argv")
70
+ argv.should_receive(:shift).and_return("update")
71
+
72
+ stdin = double("stdin")
73
+ stdin.should_receive(:read).and_return("stdin")
74
+
75
+ tasks = double("tasks")
76
+ tasks.should_receive(:totals).with("stdin").and_return("totals results")
77
+
78
+ cli = TextInvoice::CLI.new
79
+ cli.tasks = tasks
80
+ cli.stdin = stdin
81
+ cli.argv = argv
82
+
83
+ cli.run().should eql("totals results")
84
+ end
85
+
86
+ it "should return new invoice" do
87
+ argv = double("argv")
88
+ argv.should_receive(:shift).and_return("new")
89
+
90
+ tasks = double("tasks")
91
+ tasks.should_receive(:new_invoice).with(no_args()).and_return("new invoice yaml")
92
+
93
+ cli = TextInvoice::CLI.new
94
+ cli.tasks = tasks
95
+ cli.argv = argv
96
+
97
+ cli.run().should eql("new invoice yaml")
98
+ end
99
+
100
+ it "should html template invoices" do
101
+ argv = double("argv")
102
+ argv.should_receive(:shift).and_return("html")
103
+
104
+ stdin = double("stdin")
105
+ stdin.should_receive(:read).and_return("stdin")
106
+
107
+ tasks = double("tasks")
108
+ tasks.should_receive(:html).with("stdin").and_return("invoice html")
109
+
110
+ cli = TextInvoice::CLI.new
111
+ cli.tasks = tasks
112
+ cli.argv = argv
113
+ cli.stdin = stdin
114
+
115
+ cli.run().should eql("invoice html")
116
+ end
117
+
118
+ it "should custom template invoices" do
119
+ argv = double("argv")
120
+ argv.should_receive(:shift).and_return("template", "template_path")
121
+
122
+ stdin = double("stdin")
123
+ stdin.should_receive(:read).and_return("stdin")
124
+
125
+ tasks = double("tasks")
126
+ tasks.should_receive(:template).with("stdin","template_path").and_return("invoice html")
127
+
128
+ cli = TextInvoice::CLI.new
129
+ cli.tasks = tasks
130
+ cli.argv = argv
131
+ cli.stdin = stdin
132
+
133
+ cli.run().should eql("invoice html")
134
+ end
135
+
136
+ it "should return usage if custom template path not provided" do
137
+ argv = double("argv")
138
+ argv.should_receive(:shift).and_return("template", nil)
139
+
140
+ tasks = double("tasks")
141
+ tasks.should_receive(:usage)
142
+
143
+ cli = TextInvoice::CLI.new
144
+ cli.tasks = tasks
145
+ cli.argv = argv
146
+
147
+ cli.run()
148
+ end
149
+
150
+ end
@@ -0,0 +1,8 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Invoice do
4
+ it "should return invoice yaml" do
5
+ TextInvoice::Invoice.blank().should include("invoice")
6
+ end
7
+ end
8
+
@@ -0,0 +1,39 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Summary do
4
+ it "should open a yaml file and update totals" do
5
+ summary = TextInvoice::Summary.new
6
+ calculate = TextInvoice::Totals.new
7
+
8
+ invoice = summary.load("lib/text-invoice/invoice.yaml").to_yaml
9
+
10
+ blank = calculate.process(TextInvoice::Invoice.blank)
11
+ invoice.should eql(blank)
12
+ end
13
+
14
+ it "should return invoice summary" do
15
+ summary = TextInvoice::Summary.new
16
+ invoice1 = { 'due' => 5, 'total' => 10, 'paid' => 5 }
17
+ invoice2 = { 'due' => 1, 'total' => 10, 'paid' => 9 }
18
+ summary.stub(:load).and_return(invoice1, invoice2)
19
+
20
+ response = summary.summary(['file1','file2'])
21
+
22
+ headings = response.split("\n")[0].should eql(['Count','Total','Paid','Due'].join("\t"))
23
+ results = response.split("\n")[1].should eql(['2','20','14','6'].join("\t"))
24
+ end
25
+
26
+ it "should return invoice list" do
27
+ summary = TextInvoice::Summary.new
28
+ invoice1 = { 'date' => '1/3/2012', 'invoice' => "1", 'due' => 5, 'total' => 10, 'paid' => 5 }
29
+ invoice2 = { 'date' => '2/3/2012', 'invoice' => "2", 'due' => 1, 'total' => 10, 'paid' => 9 }
30
+ summary.stub(:load).and_return(invoice1, invoice2)
31
+
32
+ response = summary.list(['file1','file2'])
33
+
34
+ response.split("\n")[0].should eql(['Invoice','Date','Total','Paid','Due'].join("\t"))
35
+ response.split("\n")[1].should eql(['1','1/3/2012','10','5','5'].join("\t"))
36
+ response.split("\n")[2].should eql(['2','2/3/2012','10','9','1'].join("\t"))
37
+ end
38
+ end
39
+
@@ -0,0 +1,69 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Tasks do
4
+ it "should return usage details" do
5
+ tasks = TextInvoice::Tasks.new
6
+ tasks.usage().should include("Usage")
7
+ end
8
+
9
+ it "should return summary" do
10
+ summary_calculator = double("summary")
11
+ summary_calculator.should_receive(:summary).with([]).and_return("summary results")
12
+
13
+ tasks = TextInvoice::Tasks.new
14
+ tasks.summary_calculator = summary_calculator
15
+
16
+ tasks.summary([]).should include("summary results")
17
+ end
18
+
19
+ it "should return list" do
20
+ summary_calculator = double("summary")
21
+ summary_calculator.should_receive(:list).with([]).and_return("list results")
22
+
23
+ tasks = TextInvoice::Tasks.new
24
+ tasks.summary_calculator = summary_calculator
25
+
26
+ tasks.list([]).should include("list results")
27
+ end
28
+
29
+ it "should return totals" do
30
+ totals_calculator = double("summary")
31
+ totals_calculator.should_receive(:process).with([]).and_return("totals results")
32
+
33
+ tasks = TextInvoice::Tasks.new
34
+ tasks.totals_calculator = totals_calculator
35
+
36
+ tasks.totals([]).should include("totals results")
37
+ end
38
+
39
+ it "should return blank invoice yaml" do
40
+ invoice = double("invoice")
41
+ invoice.should_receive(:blank).and_return("new invoice yaml")
42
+
43
+ tasks = TextInvoice::Tasks.new
44
+ tasks.invoice = invoice
45
+
46
+ tasks.new_invoice().should eql("new invoice yaml")
47
+ end
48
+
49
+ it "should return html" do
50
+ template = double("template")
51
+ template.should_receive(:html).with({}).and_return("invoice html")
52
+
53
+ tasks = TextInvoice::Tasks.new
54
+ tasks.template = template
55
+
56
+ tasks.html({}).should eql("invoice html")
57
+ end
58
+
59
+ it "should return template" do
60
+ template = double("template")
61
+ template.should_receive(:custom).with({},"").and_return("invoice html")
62
+
63
+ tasks = TextInvoice::Tasks.new
64
+ tasks.template = template
65
+
66
+ tasks.template({},"").should eql("invoice html")
67
+ end
68
+
69
+ end
@@ -0,0 +1,29 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Template do
4
+ it "should return invoice html the using default template" do
5
+ template = TextInvoice::Template.new
6
+ invoice = YAML.load(TextInvoice::Invoice.blank)
7
+ invoice["invoice"] = "123"
8
+
9
+ html = template.html(invoice.to_yaml)
10
+
11
+ # check for something from the template
12
+ html.should include("html")
13
+ # check for something from the data
14
+ html.should include("123")
15
+ end
16
+
17
+ it "should return invoice html the using a custom template" do
18
+ template = TextInvoice::Template.new
19
+ invoice = YAML.load(TextInvoice::Invoice.blank)
20
+ invoice["invoice"] = "123"
21
+
22
+ html = template.custom(invoice.to_yaml, "templates/invoice.html")
23
+
24
+ # check for something from the template
25
+ html.should include("html")
26
+ # check for something from the data
27
+ html.should include("123")
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ require 'text-invoice'
2
+
3
+ describe TextInvoice::Totals do
4
+ it "should add totals for empty invoice" do
5
+ # empty invoice
6
+ invoice = {}.to_yaml
7
+ calculator = TextInvoice::Totals.new
8
+
9
+ # process
10
+ result = YAML.load(calculator.process(invoice))
11
+
12
+ # validate
13
+ result["total"].should eql(0)
14
+ result["due"].should eql(0)
15
+ end
16
+
17
+ it "should update invoice totals and subtotals" do
18
+
19
+ # setup invoice
20
+ invoice = { 'items' =>
21
+ [ { 'quantity' => 1, 'unit' => 4 },
22
+ { 'quantity' => 2, 'unit' => 3 } ] }.to_yaml
23
+ calculator = TextInvoice::Totals.new
24
+
25
+ # process
26
+ result = YAML.load(calculator.process(invoice))
27
+
28
+ # validate
29
+ result["items"][0]["subtotal"].should eql(4)
30
+ result["items"][1]["subtotal"].should eql(6)
31
+ result["total"].should eql(10)
32
+ result["due"].should eql(10)
33
+ end
34
+
35
+ it "should update 'due' amount to be 'total' minus 'paid'" do
36
+
37
+ # setup invoice
38
+ invoice = { 'items' => [ { 'quantity' => 1, 'unit' => 10 } ], 'paid' => 5 }.to_yaml
39
+ calculator = TextInvoice::Totals.new
40
+
41
+ # process
42
+ result = YAML.load(calculator.process(invoice))
43
+
44
+ # validate
45
+ result["total"].should eql(10)
46
+ result["paid"].should eql(5)
47
+ result["due"].should eql(5)
48
+ end
49
+ end
@@ -0,0 +1,261 @@
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml">
3
+ <head>
4
+ <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
5
+
6
+ <title>Invoice</title>
7
+
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ }
13
+
14
+ body {
15
+ font: 14px/1.4 Georgia, serif;
16
+ }
17
+
18
+ #page-wrap {
19
+ width: 800px;
20
+ margin: 0 auto;
21
+ }
22
+
23
+ table {
24
+ border-collapse: collapse;
25
+ }
26
+
27
+ table td, table th {
28
+ border: 1px solid black;
29
+ padding: 5px;
30
+ }
31
+
32
+ #header {
33
+ height: 15px;
34
+ width: 100%;
35
+ margin: 20px 0;
36
+ background: #222;
37
+ text-align: center;
38
+ color: white;
39
+ font: bold 15px Helvetica, Sans-Serif;
40
+ text-decoration: uppercase;
41
+ padding: 8px 0px;
42
+ }
43
+
44
+ #identity {
45
+ width: 300px;
46
+ float: left
47
+ }
48
+
49
+ #customer {
50
+ width: 300px;
51
+ float: right
52
+ }
53
+
54
+ #customer table {
55
+ width: 100%
56
+ }
57
+
58
+ #customer table th {
59
+ text-align: left;
60
+ background: #eee;
61
+ }
62
+
63
+ #address {
64
+ width: 250px;
65
+ height: 150px;
66
+ float: left;
67
+ }
68
+
69
+ #meta {
70
+ margin-top: 30px;
71
+ margin-bottom: 30px;
72
+ width: 300px;
73
+ float: right;
74
+ }
75
+
76
+ #meta td {
77
+ text-align: right;
78
+ }
79
+
80
+ #meta td.meta-head {
81
+ text-align: left;
82
+ background: #eee;
83
+ }
84
+
85
+ #meta td textarea {
86
+ width: 100%;
87
+ height: 20px;
88
+ text-align: right;
89
+ }
90
+
91
+ #items {
92
+ clear: both;
93
+ width: 100%;
94
+ margin: 30px 0 0 0;
95
+ border: 1px none black;
96
+ }
97
+
98
+ #items th {
99
+ background: #eee;
100
+ }
101
+
102
+ #items tr.item-row td {
103
+ vertical-align: top;
104
+ border: none;
105
+ }
106
+
107
+ #items tr.item-row {
108
+ border-left: 1px solid black;
109
+ border-right: 1px solid black;
110
+ }
111
+
112
+ #items tr.totals {
113
+ border-right: 1px solid black;
114
+ border-top: 1px solid black;
115
+ }
116
+
117
+ #items tr.total {
118
+ border-right: 1px solid black;
119
+ }
120
+
121
+ #items tr.due {
122
+ background: #eee;
123
+ }
124
+
125
+ #items td.description {
126
+ width: 300px;
127
+ }
128
+
129
+ #items td.blank {
130
+ border: none;
131
+ }
132
+
133
+ #items td.money {
134
+ border-right: 0;
135
+ text-align: left;
136
+ padding-left: 50px;
137
+ }
138
+
139
+ #terms {
140
+ text-align: center;
141
+ margin: 20px 0 0 0;
142
+ }
143
+
144
+ #terms h5 {
145
+ text-transform: uppercase;
146
+ font: 13px Helvetica, Sans-Serif;
147
+ border-bottom: 1px solid black;
148
+ padding: 0 0 8px 0;
149
+ margin: 0 0 8px 0;
150
+ }
151
+
152
+ #terms textarea {
153
+ width: 100%;
154
+ text-align: center;
155
+ }
156
+ </style>
157
+ </head>
158
+
159
+ <body>
160
+
161
+ <div id="page-wrap">
162
+
163
+ <div id="header">INVOICE</div>
164
+
165
+ <div id="identity">
166
+ <div>{{from.name}}</div>
167
+ <div>{{from.abn}}</div>
168
+ <br/>
169
+ <div>{{from.address1}}</div>
170
+ <div>{{from.address2}}</div>
171
+ <div>{{from.phone}}</div>
172
+ <div>{{from.email}}</div>
173
+ </div>
174
+
175
+
176
+ <div id="customer">
177
+ <table>
178
+ <tr>
179
+ <th>
180
+ To
181
+ </th>
182
+ </tr>
183
+ <tr>
184
+ <td>
185
+ <div>{{to.attn}}</div>
186
+ <div>{{to.name}}</div>
187
+ <div>{{to.address1}}</div>
188
+ <div>{{to.address2}}</div>
189
+ <div>{{to.address3}}</div>
190
+ <div>{{to.phone}}</div>
191
+ </td>
192
+ </tr>
193
+ </table>
194
+ </div>
195
+ <div style="clear:both"></div>
196
+
197
+ <div id="details">
198
+ <table id="meta">
199
+ <tr>
200
+ <td class="meta-head">Invoice #</td>
201
+ <td>{{invoice}}</td>
202
+ </tr>
203
+ <tr>
204
+
205
+ <td class="meta-head">Date</td>
206
+ <td>{{date}}</td>
207
+ </tr>
208
+ <tr>
209
+ <td class="meta-head">Amount Due</td>
210
+ <td><div class="due">${{due}}</div></td>
211
+ </tr>
212
+
213
+ </table>
214
+
215
+ </div>
216
+
217
+
218
+ <table id="items">
219
+
220
+ <tr>
221
+ <th>Description</th>
222
+ <th>Unit Cost</th>
223
+ <th>Quantity</th>
224
+ <th>Price</th>
225
+ </tr>
226
+
227
+ {{#items}}
228
+ <tr class="item-row">
229
+ <td class="description">{{description}}</td>
230
+ <td class="money">${{unit}}</td>
231
+ <td class="money">{{quantity}}</td>
232
+ <td class="money">${{subtotal}}</td>
233
+ </tr>
234
+ {{/items}}
235
+
236
+ <tr class="totals">
237
+ <td colspan="2" class="blank"> </td>
238
+ <td colspan="1" class="total-line">Total</td>
239
+ <td class="money"><div id="total">${{total}}</div></td>
240
+ </tr>
241
+ <tr class="total">
242
+ <td colspan="2" class="blank"> </td>
243
+ <td colspan="1" class="total-line">Amount Paid</td>
244
+
245
+ <td class="money">${{paid}}</td>
246
+ </tr>
247
+ <tr class="total">
248
+ <td colspan="2" class="blank"> </td>
249
+ <td colspan="1" class="total-line balance">Balance Due</td>
250
+ <td class="money"><div class="due">{{due}}</div></td>
251
+ </tr>
252
+
253
+ </table>
254
+
255
+ <div id="terms">
256
+ <h5>Terms</h5>
257
+ {{terms}}
258
+ </div>
259
+ </div>
260
+ </body>
261
+ </html>
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "text-invoice/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "text-invoice"
7
+ s.version = TextInvoice::VERSION
8
+ s.authors = ["tarn"]
9
+ s.email = ["tarn.barford@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = "invoicing tools"
12
+ s.description = ""
13
+
14
+ s.rubyforge_project = "text-invoice"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_dependency "mustache"
23
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: text-invoice
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - tarn
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-25 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70476390 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70476390
25
+ - !ruby/object:Gem::Dependency
26
+ name: mustache
27
+ requirement: &70475980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70475980
36
+ description: ''
37
+ email:
38
+ - tarn.barford@gmail.com
39
+ executables:
40
+ - text-invoice
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - README.md
47
+ - Rakefile
48
+ - bin/text-invoice
49
+ - lib/text-invoice.rb
50
+ - lib/text-invoice/cli.rb
51
+ - lib/text-invoice/invoice.rb
52
+ - lib/text-invoice/invoice.yaml
53
+ - lib/text-invoice/summary.rb
54
+ - lib/text-invoice/tasks.rb
55
+ - lib/text-invoice/template.rb
56
+ - lib/text-invoice/totals.rb
57
+ - lib/text-invoice/version.rb
58
+ - spec/cli_spec.rb
59
+ - spec/invoice_spec.rb
60
+ - spec/summary_spec.rb
61
+ - spec/tasks_spec.rb
62
+ - spec/template_spec.rb
63
+ - spec/totals_spec.rb
64
+ - templates/invoice.html
65
+ - text-invoice.gemspec
66
+ homepage: ''
67
+ licenses: []
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project: text-invoice
86
+ rubygems_version: 1.8.10
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: invoicing tools
90
+ test_files:
91
+ - spec/cli_spec.rb
92
+ - spec/invoice_spec.rb
93
+ - spec/summary_spec.rb
94
+ - spec/tasks_spec.rb
95
+ - spec/template_spec.rb
96
+ - spec/totals_spec.rb