blob: 84e5622c7f4ab121555e59d5fa5ebe943b85ff21 [file] [log] [blame]
package main
// recurrent is a tool to bill recurrent monthly invoices. It should be run at
// the beginning of each month against a database of customers stored as a
// prototext.
//
// This is a fairly janky tool, and should be replaced by a proper billing
// service.
//
// $ bazel run //bgpwtf/invoice/recurrent -- \
// -invoice_configuration=$(pwd)bgpwtf/invoice/customers.pb.text \
// -invoice_service 10.78.253.10:4200 -hspki_disable
//
// q3k has the sqlite database for the invoice service and the customer
// prototext.
import (
"bufio"
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"time"
"github.com/golang/glog"
"github.com/golang/protobuf/proto"
"google.golang.org/grpc"
"code.hackerspace.pl/hscloud/go/pki"
pb "code.hackerspace.pl/hscloud/bgpwtf/invoice/proto"
)
func init() {
flag.Set("logtostderr", "true")
}
var (
flagConfiguration string
flagService string
)
func main() {
flag.StringVar(&flagService, "invoice_service", "127.0.0.1:4200", "Address of invoice service")
flag.StringVar(&flagConfiguration, "invoice_configuration", "customers.pb.text", "Prototext of customer data")
flag.Parse()
if flagConfiguration == "" {
glog.Exit("-invoice_configuration must be set")
}
cfgBytes, err := ioutil.ReadFile(flagConfiguration)
if err != nil {
glog.Exitf("could not read configuration: %v", err)
}
var cfg pb.Configuration
if err := proto.UnmarshalText(string(cfgBytes), &cfg); err != nil {
glog.Exitf("UnmarshalText: %v", err)
}
conn, err := grpc.Dial(flagService, pki.WithClientHSPKI())
if err != nil {
glog.Exitf("Dial(%q): %v", flagService, err)
return
}
svc := pb.NewInvoicerClient(conn)
ctx := context.Background()
var created []string
now := time.Now()
for _, sub := range cfg.Subscription {
glog.Infof("Emitting for %q...", sub.Template.CustomerBilling[0])
data := sub.Template
if data.Date == 0 {
data.Date = now.UnixNano()
}
date := time.Unix(0, data.Date)
year := int(date.Year())
month := int(date.Month())
switch sub.Cycle {
case pb.Subscription_CYCLE_CURRENT:
case pb.Subscription_CYCLE_PREV:
month -= 1
if month < 1 {
month = 12
year -= 1
}
default:
glog.Exitf("Invalid cycle: %v", sub.Cycle)
}
for _, item := range data.Item {
item.Title = strings.ReplaceAll(item.Title, "%M", fmt.Sprintf("%02d", month))
item.Title = strings.ReplaceAll(item.Title, "%Y", fmt.Sprintf("%04d", year))
}
res, err := svc.CreateInvoice(ctx, &pb.CreateInvoiceRequest{
InvoiceData: data,
})
if err != nil {
glog.Exitf("CreateInvoice: %v", err)
}
glog.Infof("Created invoice %q", res.Uid)
created = append(created, res.Uid)
}
reader := bufio.NewReader(os.Stdin)
fmt.Print("Invoices generated. Seal? [Yn]")
text, err := reader.ReadString('\n')
if err != nil {
glog.Exitf("Response: %v", err)
}
switch strings.TrimSpace(strings.ToLower(text)) {
case "", "y":
default:
glog.Exitf("Aborting.")
}
for _, uid := range created {
glog.Infof("Sealing %q...", uid)
_, err := svc.SealInvoice(ctx, &pb.SealInvoiceRequest{
Uid: uid,
DateSource: pb.SealInvoiceRequest_DATE_SOURCE_PROFORMA,
Language: "pl",
})
if err != nil {
glog.Errorf("Sealing %q failed: %v", uid, err)
continue
}
res, err := svc.GetInvoice(ctx, &pb.GetInvoiceRequest{
Uid: uid,
})
if err != nil {
glog.Errorf("Retrieving sealed invoice %q failed: %v", uid, err)
continue
}
fuid := res.Invoice.FinalUid
glog.Infof("%q: Final UID: %s", uid, fuid)
stream, err := svc.RenderInvoice(ctx, &pb.RenderInvoiceRequest{
Uid: uid,
})
if err != nil {
glog.Errorf("Rendering sealed invoice failed: %v", err)
continue
}
path := fmt.Sprintf("/tmp/%s.pdf", strings.ReplaceAll(fuid, "/", ""))
glog.Infof("Downloading %s...", path)
f, err := os.Create(path)
if err != nil {
glog.Errorf("Create: %v", err)
continue
}
for {
block, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
glog.Errorf("Recv: %v", err)
break
}
if _, err := f.Write(block.Data); err != nil {
glog.Errorf("Write: %v", err)
break
}
}
f.Close()
}
}