forked from hswaw/hscloud
175 lines
4.1 KiB
Go
175 lines
4.1 KiB
Go
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()
|
|
}
|
|
|
|
}
|