1package main
2
3import (
4 _ "embed"
5 "flag"
6 "fmt"
7 "log"
8 "strings"
9 "time"
10
11 "github.com/signintech/gopdf"
12 "github.com/spf13/cobra"
13 "github.com/spf13/viper"
14)
15
16//go:embed Inter.ttf
17var interFont []byte
18
19//go:embed Inter-Bold.ttf
20var interBoldFont []byte
21
22type Invoice struct {
23 Id string `json:"id" yaml:"id"`
24 Title string `json:"title" yaml:"title"`
25
26 Logo string `json:"logo" yaml:"logo"`
27 From string `json:"from" yaml:"from"`
28 To string `json:"to" yaml:"to"`
29 Date string `json:"date" yaml:"date"`
30 Due string `json:"due" yaml:"due"`
31
32 Items []string `json:"items" yaml:"items"`
33 Quantities []int `json:"quantities" yaml:"quantities"`
34 Rates []float64 `json:"rates" yaml:"rates"`
35
36 Tax float64 `json:"tax" yaml:"tax"`
37 Discount float64 `json:"discount" yaml:"discount"`
38 Currency string `json:"currency" yaml:"currency"`
39
40 Note string `json:"note" yaml:"note"`
41}
42
43func DefaultInvoice() Invoice {
44 return Invoice{
45 Id: time.Now().Format("20060102"),
46 Title: "INVOICE",
47 Rates: []float64{25},
48 Quantities: []int{2},
49 Items: []string{"Paper Cranes"},
50 From: "Project Folded, Inc.",
51 To: "Untitled Corporation, Inc.",
52 Date: time.Now().Format("Jan 02, 2006"),
53 Due: time.Now().AddDate(0, 0, 14).Format("Jan 02, 2006"),
54 Tax: 0,
55 Discount: 0,
56 Currency: "USD",
57 }
58}
59
60var (
61 importPath string
62 output string
63 file = Invoice{}
64 defaultInvoice = DefaultInvoice()
65)
66
67func init() {
68 viper.AutomaticEnv()
69
70 generateCmd.Flags().StringVar(&importPath, "import", "", "Imported file (.json/.yaml)")
71 generateCmd.Flags().StringVar(&file.Id, "id", time.Now().Format("20060102"), "ID")
72 generateCmd.Flags().StringVar(&file.Title, "title", "INVOICE", "Title")
73
74 generateCmd.Flags().Float64SliceVarP(&file.Rates, "rate", "r", defaultInvoice.Rates, "Rates")
75 generateCmd.Flags().IntSliceVarP(&file.Quantities, "quantity", "q", defaultInvoice.Quantities, "Quantities")
76 generateCmd.Flags().StringSliceVarP(&file.Items, "item", "i", defaultInvoice.Items, "Items")
77
78 generateCmd.Flags().StringVarP(&file.Logo, "logo", "l", defaultInvoice.Logo, "Company logo")
79 generateCmd.Flags().StringVarP(&file.From, "from", "f", defaultInvoice.From, "Issuing company")
80 generateCmd.Flags().StringVarP(&file.To, "to", "t", defaultInvoice.To, "Recipient company")
81 generateCmd.Flags().StringVar(&file.Date, "date", defaultInvoice.Date, "Date")
82 generateCmd.Flags().StringVar(&file.Due, "due", defaultInvoice.Due, "Payment due date")
83
84 generateCmd.Flags().Float64Var(&file.Tax, "tax", defaultInvoice.Tax, "Tax")
85 generateCmd.Flags().Float64VarP(&file.Discount, "discount", "d", defaultInvoice.Discount, "Discount")
86 generateCmd.Flags().StringVarP(&file.Currency, "currency", "c", defaultInvoice.Currency, "Currency")
87
88 generateCmd.Flags().StringVarP(&file.Note, "note", "n", "", "Note")
89 generateCmd.Flags().StringVarP(&output, "output", "o", "invoice.pdf", "Output file (.pdf)")
90
91 flag.Parse()
92}
93
94var rootCmd = &cobra.Command{
95 Use: "invoice",
96 Short: "Invoice generates invoices from the command line.",
97 Long: `Invoice generates invoices from the command line.`,
98}
99
100var generateCmd = &cobra.Command{
101 Use: "generate",
102 Short: "Generate an invoice",
103 Long: `Generate an invoice`,
104 RunE: func(cmd *cobra.Command, args []string) error {
105
106 if importPath != "" {
107 err := importData(importPath, &file, cmd.Flags())
108 if err != nil {
109 return err
110 }
111 }
112
113 pdf := gopdf.GoPdf{}
114 pdf.Start(gopdf.Config{
115 PageSize: *gopdf.PageSizeA4,
116 })
117 pdf.SetMargins(40, 40, 40, 40)
118 pdf.AddPage()
119 err := pdf.AddTTFFontData("Inter", interFont)
120 if err != nil {
121 return err
122 }
123
124 err = pdf.AddTTFFontData("Inter-Bold", interBoldFont)
125 if err != nil {
126 return err
127 }
128
129 writeLogo(&pdf, file.Logo, file.From)
130 writeTitle(&pdf, file.Title, file.Id, file.Date)
131 writeBillTo(&pdf, file.To)
132 writeHeaderRow(&pdf)
133 subtotal := 0.0
134 for i := range file.Items {
135 q := 1
136 if len(file.Quantities) > i {
137 q = file.Quantities[i]
138 }
139
140 r := 0.0
141 if len(file.Rates) > i {
142 r = file.Rates[i]
143 }
144
145 writeRow(&pdf, file.Items[i], q, r)
146 subtotal += float64(q) * r
147 }
148 if file.Note != "" {
149 writeNotes(&pdf, file.Note)
150 }
151 writeTotals(&pdf, subtotal, subtotal*file.Tax, subtotal*file.Discount)
152 if file.Due != "" {
153 writeDueDate(&pdf, file.Due)
154 }
155 writeFooter(&pdf, file.Id)
156 output = strings.TrimSuffix(output, ".pdf") + ".pdf"
157 err = pdf.WritePdf(output)
158 if err != nil {
159 return err
160 }
161
162 fmt.Printf("Generated %s\n", output)
163
164 return nil
165 },
166}
167
168func main() {
169 rootCmd.AddCommand(generateCmd)
170 err := rootCmd.Execute()
171 if err != nil {
172 log.Fatal(err)
173 }
174}