main.go

  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}