Adds an invoice number based on a client.id and the current YEARMONTH
4 files changed, 50 insertions(+), 24 deletions(-)

M README.md
M data.go
M invoice.tmpl
M main.go
M README.md +4 -0
@@ 127,6 127,10 @@ Set your client's contact info with the 
 
 :   string, the second line of your client's address
 
+<client>.id
+
+:   string, an ID used in generating a unique invoice number
+
 E.g.
 
     timew config megacorp.company "MegaCorp, Inc"

          
M data.go +39 -22
@@ 6,6 6,8 @@ import (
 	"bytes"
 	"compress/gzip"
 	"encoding/base64"
+	"fmt"
+	"io"
 	"io/ioutil"
 	"net/http"
 	"os"

          
@@ 100,7 102,24 @@ func (f *_escFile) Close() error {
 }
 
 func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) {
-	return nil, nil
+	if !f.isDir {
+		return nil, fmt.Errorf(" escFile.Readdir: '%s' is not directory", f.name)
+	}
+
+	fis, ok := _escDirs[f.local]
+	if !ok {
+		return nil, fmt.Errorf(" escFile.Readdir: '%s' is directory, but we have no info about content of this dir, local=%s", f.name, f.local)
+	}
+	limit := count
+	if count <= 0 || limit > len(fis) {
+		limit = len(fis)
+	}
+
+	if len(fis) == 0 && count > 0 {
+		return nil, io.EOF
+	}
+
+	return fis[0:limit], nil
 }
 
 func (f *_escFile) Stat() (os.FileInfo, error) {

          
@@ 191,29 210,27 @@ func FSMustString(useLocal bool, name st
 var _escData = map[string]*_escFile{
 
 	"/invoice.tmpl": {
+		name:    "invoice.tmpl",
 		local:   "invoice.tmpl",
-		size:    1770,
-		modtime: 1577834313,
+		size:    1816,
+		modtime: 1589222202,
 		compressed: `
-H4sIAAAAAAAC/7RUTW8bNxA9i7/iITqkBYx1rDRAkRP14cABDFuVVBQ5cndHElEuuSC5dlSW/73gUvZK
-jt2eypM0b+a9x5nh8vXBfdWV6mpCQG0qRHYe86VKsYWpuoa0ZwD/qqWXQn0x2iNgIxtymAlHuJq0iMA4
-xy4wN52VZC9wQ+qBvKzEBYqiSBxzo7pG33VNSRYBV6nu9IxxxMwWVZ/r8NPkAh97hp8TxVLs6N5K0l54
-aTQClsZ6K6RHfMJvSNRkHQLWsmkVZeR4gVuhd53YpVte652Sbp/h+9bLRv5FiSCV3pkzd2N8I3d5Z+Aa
-YT3axFBaEn9iS8J3ltjlJeMb+u7BZ7STmrExZnIHLRqC0fCmZXwhXavEATxZlHqHgDD0CTOjakx+Sf3k
-x0aH0ByKO9FQjIhpJl86pf6Qtd+vOkVJ5JvpLCqjvag8pN4a2/StYfyWtn5QvP7eCl0jsBDIVaIlNIdi
-WteWnLuKEXwld/ssOeDLvdFZmt8uX6ucvFV53Qipnivj4FSl4b13/205MD5DwFf9YGRF2JjPiWx5YiNz
-FXPTtEIfYkard5fvDH7M6dXi6wynbXgbnsSYLsIXy97ZQnj6nAt4+p0RNsYNWXrv4PcEeTTvRamI8XtN
-K/MIvikV2Gj8vF0LcpWVbb/RZos12QdZkcNvndBe+gNenN+19FjaxPz2mTam056NRO4vAviclMJj2h6E
-X6sIPsMUf5/HJ8WnjMxeR6SuKa1m5W2fNn+Z9vGVpAUiG5UvjAzSg9RTIWy/U/GE/gcokQbGV+ZRsJHt
-FInSPKSHfSCX9FKoJGUez0N91tErPhQfJtuz5BPkU49MEd4cUGSjtKPPc4psNEc4nU9kowXCcRiIjIVg
-hd4Rihh772VWCKFISxTjsLrFVGuTv3PpIWWpEIob01mXI/McWeXKo1YIxborN8aL/gGmddZ1TJv7v7Xq
-6rlT6670SXnw2//tPac7P7kezOb0DB0dJqKZUEJXhEVHT1z/XpsueLtkjF/rGv2nmP0TAAD//1j+p/vq
-BgAA
+H4sIAAAAAAAC/7RUzW7jNhddm09xMF7M9wGpMvF0gCIr+ieDBAgS13ZRzJKSrm2iFCmQVDIuy3cvKCmW
+nUnaVbUxfM+955z7I/H1wd3pQjUlIaA0BSI7j/lcpdjCFE1F2jOA32nppVBfjfYI2MiKHGbCEa4mNSIw
+7mIXmJvGSrIXuCX1RF4W4gJZliWOuVFNpR+aKieLgKtUd/qM0WNmi6LNdfjf5AKfW4b/J4ql2NGjlaS9
+8NJoBCyN9VZIj/iC35IoyToErGVVK+qQvoF7oXeN2KUub/ROSbfv4Mfay0r+SYkglT6YM3djfCN3+WDg
+KmE96sSQWxJ/YEvCN5bY5SXjG/ruwWe0k5qxMWZyBy0qgtHwpmZ8IV2txAE8WZR6h4AwzAkzo0pMfk7z
+5P2gQ6gO2YOoKEbEtJOvjVK/y9LvV42iJPLNNBaF0V4UHlJvja3a0TB+T1s/KN58r4UuEVgI5ApRE6pD
+Ni1LS85dxQi+krt9Jzngy73RnTS/X75VOXmv8qYSUh0r4+BUpeV9dP9uOTA+Q8CdfjKyIGzMdSJbntjo
+uLK5qWqhD7FDiw+XHwx+zGnV4tsMp2N4H57EmBrhi2XrbCE8XUfw9HucAZ+F3vB1RAg9wV0Z408hVEb7
+fexFWh42xi1Z+ujg9wTZt+pFrojxR00r8wy+yRXYaHy8xQW5wsq6vX+zxZrskyzI4ddGaC/9Aa+e37T0
+WNrE/P4zrUyjPRuJbhsI4HNSCs/p1hB+KSL4DFP8dR6fZF86ZPY2InVJ6ZALb9u0+eu0z28kLRDZKH9l
+ZJAepF4KYdvpxxP6H6BEGhhfmWfBRrZRJHLzlD4DB3JJL4VyUub5PNRm9V7xKfs02Z4lnyBfWmSK8O6C
+Ihuliz7uKbLRHOF0P5GNFgj9MhAZC8EKvSNkMbbe804hhCydXYzDoWdTrU33VUyvXScVQnZrGuu6yLyL
+rLrKXiuEbN3kG+NF+7qm49dlTHf+n43q6jipdZP7pDz4bf+2nlPPL64Hs116B/UOE9FMKKELwqKhF65/
+rk0N3qe38EaXaD/c7O8AAAD//+ueyO4YBwAA
 `,
 	},
+}
 
-	"/": {
-		isDir: true,
-		local: "",
-	},
-}
+var _escDirs = map[string][]os.FileInfo{}

          
M invoice.tmpl +1 -2
@@ 30,8 30,7 @@ c"/"o {{escape client.Contact}} @PP
 }
 
 @DP
-@B {Date:} @PP
-@Date
+@B {Date:} @Date @Right {@B{Invoice:} {{client.Id}}-{{month}}} @PP
 
 @DP
 

          
M main.go +6 -0
@@ 76,6 76,7 @@ func main() {
 		CLIENT.Address1 = config[client+".address1"]
 		CLIENT.Address2 = config[client+".address2"]
 		CLIENT.Phone = config[client+".phone"]
+		CLIENT.Id = config[client+".id"]
 	}
 	if p {
 		fmt.Fprintf(os.Stdin, "error isPrefix = %v", p)

          
@@ 102,6 103,7 @@ func main() {
 	if err != nil {
 		panic(err)
 	}
+	end := records[len(records)-1].End
 
 	hours := func(rs []Record) float64 {
 		var ttl float64

          
@@ 134,6 136,9 @@ func main() {
 		"escape": func(s string) string {
 			return replacer.Replace(s)
 		},
+		"month": func() string {
+			return end.t.Format("200601")
+		},
 	}).Parse(string(f)))
 	err = t.Execute(os.Stdout, records)
 	if err != nil {

          
@@ 155,6 160,7 @@ type To struct {
 	Address1 string
 	Address2 string
 	Phone    string
+	Id       string
 }
 
 type Record struct {